@jupyterlite/ai 0.4.0 → 0.6.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 (72) hide show
  1. package/lib/chat-handler.d.ts +9 -1
  2. package/lib/chat-handler.js +37 -1
  3. package/lib/completion-provider.d.ts +8 -1
  4. package/lib/components/stop-button.d.ts +19 -0
  5. package/lib/components/stop-button.js +32 -0
  6. package/lib/{llm-models/anthropic-completer.d.ts → default-providers/Anthropic/completer.d.ts} +1 -1
  7. package/lib/{llm-models/anthropic-completer.js → default-providers/Anthropic/completer.js} +1 -1
  8. package/lib/{llm-models/chrome-completer.d.ts → default-providers/ChromeAI/completer.d.ts} +1 -1
  9. package/lib/{llm-models/chrome-completer.js → default-providers/ChromeAI/completer.js} +1 -1
  10. package/lib/default-providers/ChromeAI/instructions.d.ts +2 -0
  11. package/lib/default-providers/ChromeAI/instructions.js +24 -0
  12. package/lib/{llm-models/codestral-completer.d.ts → default-providers/MistralAI/completer.d.ts} +1 -1
  13. package/lib/{llm-models/codestral-completer.js → default-providers/MistralAI/completer.js} +1 -1
  14. package/lib/default-providers/MistralAI/instructions.d.ts +2 -0
  15. package/lib/default-providers/MistralAI/instructions.js +16 -0
  16. package/lib/{llm-models/openai-completer.d.ts → default-providers/OpenAI/completer.d.ts} +1 -1
  17. package/lib/{llm-models/openai-completer.js → default-providers/OpenAI/completer.js} +1 -1
  18. package/lib/default-providers/index.d.ts +2 -0
  19. package/lib/default-providers/index.js +60 -0
  20. package/lib/index.d.ts +3 -2
  21. package/lib/index.js +57 -36
  22. package/lib/provider.d.ts +13 -12
  23. package/lib/provider.js +43 -9
  24. package/lib/settings/index.d.ts +3 -0
  25. package/lib/settings/index.js +3 -0
  26. package/lib/settings/panel.d.ts +17 -0
  27. package/lib/settings/panel.js +92 -5
  28. package/lib/settings/settings-connector.d.ts +31 -0
  29. package/lib/settings/settings-connector.js +61 -0
  30. package/lib/settings/utils.d.ts +3 -0
  31. package/lib/settings/utils.js +5 -0
  32. package/lib/tokens.d.ts +16 -4
  33. package/package.json +14 -7
  34. package/schema/provider-registry.json +6 -0
  35. package/src/chat-handler.ts +43 -1
  36. package/src/completion-provider.ts +8 -1
  37. package/src/components/stop-button.tsx +56 -0
  38. package/src/{llm-models/anthropic-completer.ts → default-providers/Anthropic/completer.ts} +2 -2
  39. package/src/{llm-models/chrome-completer.ts → default-providers/ChromeAI/completer.ts} +3 -2
  40. package/src/default-providers/ChromeAI/instructions.ts +24 -0
  41. package/src/{llm-models/codestral-completer.ts → default-providers/MistralAI/completer.ts} +2 -2
  42. package/src/default-providers/MistralAI/instructions.ts +16 -0
  43. package/src/{llm-models/openai-completer.ts → default-providers/OpenAI/completer.ts} +2 -2
  44. package/src/default-providers/index.ts +71 -0
  45. package/src/index.ts +77 -49
  46. package/src/provider.ts +58 -15
  47. package/src/settings/index.ts +3 -0
  48. package/src/settings/panel.tsx +109 -5
  49. package/src/settings/settings-connector.ts +89 -0
  50. package/src/settings/utils.ts +6 -0
  51. package/src/tokens.ts +17 -4
  52. package/lib/llm-models/index.d.ts +0 -4
  53. package/lib/llm-models/index.js +0 -43
  54. package/lib/settings/instructions.d.ts +0 -2
  55. package/lib/settings/instructions.js +0 -44
  56. package/lib/settings/schemas/index.d.ts +0 -3
  57. package/lib/settings/schemas/index.js +0 -11
  58. package/lib/slash-commands.d.ts +0 -16
  59. package/lib/slash-commands.js +0 -25
  60. package/src/llm-models/index.ts +0 -50
  61. package/src/settings/instructions.ts +0 -48
  62. package/src/settings/schemas/index.ts +0 -15
  63. package/src/slash-commands.tsx +0 -55
  64. /package/lib/{llm-models/base-completer.d.ts → base-completer.d.ts} +0 -0
  65. /package/lib/{llm-models/base-completer.js → base-completer.js} +0 -0
  66. /package/lib/{settings/schemas/_generated/Anthropic.json → default-providers/Anthropic/settings-schema.json} +0 -0
  67. /package/lib/{settings/schemas/_generated/ChromeAI.json → default-providers/ChromeAI/settings-schema.json} +0 -0
  68. /package/lib/{settings/schemas/_generated/MistralAI.json → default-providers/MistralAI/settings-schema.json} +0 -0
  69. /package/lib/{settings/schemas/_generated/OpenAI.json → default-providers/OpenAI/settings-schema.json} +0 -0
  70. /package/lib/settings/{schemas/base.json → base.json} +0 -0
  71. /package/src/{llm-models/base-completer.ts → base-completer.ts} +0 -0
  72. /package/src/{llm-models/svg.d.ts → global.d.ts} +0 -0
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import StopIcon from '@mui/icons-material/Stop';
7
+ import React from 'react';
8
+
9
+ import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';
10
+
11
+ /**
12
+ * Properties of the stop button.
13
+ */
14
+ export interface IStopButtonProps
15
+ extends InputToolbarRegistry.IToolbarItemProps {
16
+ /**
17
+ * The function to stop streaming.
18
+ */
19
+ stopStreaming: () => void;
20
+ }
21
+
22
+ /**
23
+ * The stop button.
24
+ */
25
+ export function StopButton(props: IStopButtonProps): JSX.Element {
26
+ const tooltip = 'Stop streaming';
27
+ return (
28
+ <TooltippedButton
29
+ onClick={props.stopStreaming}
30
+ tooltip={tooltip}
31
+ buttonProps={{
32
+ size: 'small',
33
+ variant: 'contained',
34
+ title: tooltip
35
+ }}
36
+ >
37
+ <StopIcon />
38
+ </TooltippedButton>
39
+ );
40
+ }
41
+
42
+ /**
43
+ * factory returning the toolbar item.
44
+ */
45
+ export function stopItem(
46
+ stopStreaming: () => void
47
+ ): InputToolbarRegistry.IToolbarItem {
48
+ return {
49
+ element: (props: InputToolbarRegistry.IToolbarItemProps) => {
50
+ const stopProps: IStopButtonProps = { ...props, stopStreaming };
51
+ return StopButton(stopProps);
52
+ },
53
+ position: 50,
54
+ hidden: true /* hidden by default */
55
+ };
56
+ }
@@ -6,8 +6,8 @@ import { ChatAnthropic } from '@langchain/anthropic';
6
6
  import { BaseChatModel } from '@langchain/core/language_models/chat_models';
7
7
  import { AIMessage, SystemMessage } from '@langchain/core/messages';
8
8
 
9
- import { BaseCompleter, IBaseCompleter } from './base-completer';
10
- import { COMPLETION_SYSTEM_PROMPT } from '../provider';
9
+ import { BaseCompleter, IBaseCompleter } from '../../base-completer';
10
+ import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
11
11
 
12
12
  export class AnthropicCompleter implements IBaseCompleter {
13
13
  constructor(options: BaseCompleter.IOptions) {
@@ -5,8 +5,9 @@ import {
5
5
  import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
6
6
  import { LLM } from '@langchain/core/language_models/llms';
7
7
  import { HumanMessage, SystemMessage } from '@langchain/core/messages';
8
- import { BaseCompleter, IBaseCompleter } from './base-completer';
9
- import { COMPLETION_SYSTEM_PROMPT } from '../provider';
8
+
9
+ import { BaseCompleter, IBaseCompleter } from '../../base-completer';
10
+ import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
10
11
 
11
12
  /**
12
13
  * Regular expression to match the '```' string at the start of a string.
@@ -0,0 +1,24 @@
1
+ export default `
2
+ <i class="fas fa-exclamation-triangle"></i> Support for ChromeAI is still experimental and only available in Google Chrome.
3
+
4
+ You can test ChromeAI is enabled in your browser by going to the following URL: <https://chromeai.org/>
5
+
6
+ Enable the proper flags in Google Chrome.
7
+
8
+ - chrome://flags/#prompt-api-for-gemini-nano
9
+ - Select: \`Enabled\`
10
+ - chrome://flags/#optimization-guide-on-device-model
11
+ - Select: \`Enabled BypassPrefRequirement\`
12
+ - chrome://components
13
+ - Click \`Check for Update\` on Optimization Guide On Device Model to download the model
14
+ - [Optional] chrome://flags/#text-safety-classifier
15
+
16
+ <img src="https://github.com/user-attachments/assets/d48f46cc-52ee-4ce5-9eaf-c763cdbee04c" alt="A screenshot showing how to enable the ChromeAI flag in Google Chrome" width="500px">
17
+
18
+ Then restart Chrome for these changes to take effect.
19
+
20
+ <i class="fas fa-exclamation-triangle"></i> On first use, Chrome will download the on-device model, which can be as large as 22GB (according to their docs and at the time of writing).
21
+ During the download, ChromeAI may not be available via the extension.
22
+
23
+ <i class="fa fa-info-circle" aria-hidden="true"></i> For more information about Chrome Built-in AI: <https://developer.chrome.com/docs/ai/get-started>
24
+ `;
@@ -11,8 +11,8 @@ import {
11
11
  import { ChatMistralAI } from '@langchain/mistralai';
12
12
  import { Throttler } from '@lumino/polling';
13
13
 
14
- import { BaseCompleter, IBaseCompleter } from './base-completer';
15
- import { COMPLETION_SYSTEM_PROMPT } from '../provider';
14
+ import { BaseCompleter, IBaseCompleter } from '../../base-completer';
15
+ import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
16
16
 
17
17
  /**
18
18
  * The Mistral API has a rate limit of 1 request per second
@@ -0,0 +1,16 @@
1
+ export default `
2
+ <i class="fas fa-exclamation-triangle"></i> This extension is still very much experimental. It is not an official MistralAI extension.
3
+
4
+ 1. Go to <https://console.mistral.ai/api-keys/> and create an API key.
5
+
6
+ <img src="https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/1-api-key.png" alt="Screenshot showing how to create an API key" width="500px">
7
+
8
+ 2. Open the JupyterLab settings and go to the **Ai providers** section to select the \`MistralAI\`
9
+ provider and the API key (required).
10
+
11
+ <img src="https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/2-jupyterlab-settings.png" alt="Screenshot showing how to add the API key to the settings" width="500px">
12
+
13
+ 3. Open the chat, or use the inline completer
14
+
15
+ <img src="https://raw.githubusercontent.com/jupyterlite/ai/refs/heads/main/img/3-usage.png" alt="Screenshot showing how to use the chat" width="500px">
16
+ `;
@@ -6,8 +6,8 @@ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
6
6
  import { AIMessage, SystemMessage } from '@langchain/core/messages';
7
7
  import { ChatOpenAI } from '@langchain/openai';
8
8
 
9
- import { BaseCompleter, IBaseCompleter } from './base-completer';
10
- import { COMPLETION_SYSTEM_PROMPT } from '../provider';
9
+ import { BaseCompleter, IBaseCompleter } from '../../base-completer';
10
+ import { COMPLETION_SYSTEM_PROMPT } from '../../provider';
11
11
 
12
12
  export class OpenAICompleter implements IBaseCompleter {
13
13
  constructor(options: BaseCompleter.IOptions) {
@@ -0,0 +1,71 @@
1
+ import {
2
+ JupyterFrontEnd,
3
+ JupyterFrontEndPlugin
4
+ } from '@jupyterlab/application';
5
+ import { ChatAnthropic } from '@langchain/anthropic';
6
+ import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
7
+ import { ChatMistralAI } from '@langchain/mistralai';
8
+ import { ChatOpenAI } from '@langchain/openai';
9
+
10
+ import { IAIProvider, IAIProviderRegistry } from '../tokens';
11
+
12
+ // Import completers
13
+ import { AnthropicCompleter } from './Anthropic/completer';
14
+ import { ChromeCompleter } from './ChromeAI/completer';
15
+ import { CodestralCompleter } from './MistralAI/completer';
16
+ import { OpenAICompleter } from './OpenAI/completer';
17
+
18
+ // Import Settings
19
+ import AnthropicSettings from './Anthropic/settings-schema.json';
20
+ import ChromeAISettings from './ChromeAI/settings-schema.json';
21
+ import MistralAISettings from './MistralAI/settings-schema.json';
22
+ import OpenAISettings from './OpenAI/settings-schema.json';
23
+
24
+ // Import instructions
25
+ import ChromeAIInstructions from './ChromeAI/instructions';
26
+ import MistralAIInstructions from './MistralAI/instructions';
27
+
28
+ // Build the AIProvider list
29
+ const AIProviders: IAIProvider[] = [
30
+ {
31
+ name: 'Anthropic',
32
+ chatModel: ChatAnthropic,
33
+ completer: AnthropicCompleter,
34
+ settingsSchema: AnthropicSettings,
35
+ errorMessage: (error: any) => error.error.error.message
36
+ },
37
+ {
38
+ name: 'ChromeAI',
39
+ // TODO: fix
40
+ // @ts-expect-error: missing properties
41
+ chatModel: ChromeAI,
42
+ completer: ChromeCompleter,
43
+ instructions: ChromeAIInstructions,
44
+ settingsSchema: ChromeAISettings
45
+ },
46
+ {
47
+ name: 'MistralAI',
48
+ chatModel: ChatMistralAI,
49
+ completer: CodestralCompleter,
50
+ instructions: MistralAIInstructions,
51
+ settingsSchema: MistralAISettings
52
+ },
53
+ {
54
+ name: 'OpenAI',
55
+ chatModel: ChatOpenAI,
56
+ completer: OpenAICompleter,
57
+ settingsSchema: OpenAISettings
58
+ }
59
+ ];
60
+
61
+ export const defaultProviderPlugins: JupyterFrontEndPlugin<void>[] =
62
+ AIProviders.map(provider => {
63
+ return {
64
+ id: `@jupyterlite/ai:${provider.name}`,
65
+ autoStart: true,
66
+ requires: [IAIProviderRegistry],
67
+ activate: (app: JupyterFrontEnd, registry: IAIProviderRegistry) => {
68
+ registry.add(provider);
69
+ }
70
+ };
71
+ });
package/src/index.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  ActiveCellManager,
3
- AutocompletionRegistry,
4
3
  buildChatSidebar,
5
4
  buildErrorWidget,
5
+ ChatCommandRegistry,
6
6
  IActiveCellManager,
7
- IAutocompletionCommandsProps,
8
- IAutocompletionRegistry
7
+ IChatCommandRegistry,
8
+ InputToolbarRegistry
9
9
  } from '@jupyter/chat';
10
10
  import {
11
11
  JupyterFrontEnd,
@@ -15,56 +15,45 @@ import { ReactWidget, IThemeManager } from '@jupyterlab/apputils';
15
15
  import { ICompletionProviderManager } from '@jupyterlab/completer';
16
16
  import { INotebookTracker } from '@jupyterlab/notebook';
17
17
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
18
- import { ISettingRegistry } from '@jupyterlab/settingregistry';
18
+ import {
19
+ ISettingConnector,
20
+ ISettingRegistry
21
+ } from '@jupyterlab/settingregistry';
19
22
  import { IFormRendererRegistry } from '@jupyterlab/ui-components';
20
23
  import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
24
+ import { ISecretsManager } from 'jupyter-secrets-manager';
21
25
 
22
26
  import { ChatHandler } from './chat-handler';
23
27
  import { CompletionProvider } from './completion-provider';
24
- import { AIProviders } from './llm-models';
28
+ import { defaultProviderPlugins } from './default-providers';
25
29
  import { AIProviderRegistry } from './provider';
26
- import { aiSettingsRenderer } from './settings/panel';
27
- import { renderSlashCommandOption } from './slash-commands';
30
+ import { aiSettingsRenderer, SettingConnector } from './settings';
28
31
  import { IAIProviderRegistry } from './tokens';
32
+ import { stopItem } from './components/stop-button';
29
33
 
30
- const autocompletionRegistryPlugin: JupyterFrontEndPlugin<IAutocompletionRegistry> =
31
- {
32
- id: '@jupyterlite/ai:autocompletion-registry',
33
- description: 'Autocompletion registry',
34
- autoStart: true,
35
- provides: IAutocompletionRegistry,
36
- activate: () => {
37
- const autocompletionRegistry = new AutocompletionRegistry();
38
- const options = ['/clear'];
39
- const autocompletionCommands: IAutocompletionCommandsProps = {
40
- opener: '/',
41
- commands: options.map(option => {
42
- return {
43
- id: option.slice(1),
44
- label: option,
45
- description: 'Clear the chat window'
46
- };
47
- }),
48
- props: {
49
- renderOption: renderSlashCommandOption
50
- }
51
- };
52
- autocompletionRegistry.add('jupyterlite-ai', autocompletionCommands);
53
- return autocompletionRegistry;
54
- }
55
- };
34
+ const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
35
+ id: '@jupyterlite/ai:autocompletion-registry',
36
+ description: 'Autocompletion registry',
37
+ autoStart: true,
38
+ provides: IChatCommandRegistry,
39
+ activate: () => {
40
+ const registry = new ChatCommandRegistry();
41
+ registry.addProvider(new ChatHandler.ClearCommandProvider());
42
+ return registry;
43
+ }
44
+ };
56
45
 
57
46
  const chatPlugin: JupyterFrontEndPlugin<void> = {
58
47
  id: '@jupyterlite/ai:chat',
59
48
  description: 'LLM chat extension',
60
49
  autoStart: true,
61
- requires: [IAIProviderRegistry, IRenderMimeRegistry, IAutocompletionRegistry],
50
+ requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry],
62
51
  optional: [INotebookTracker, ISettingRegistry, IThemeManager],
63
52
  activate: async (
64
53
  app: JupyterFrontEnd,
65
54
  providerRegistry: IAIProviderRegistry,
66
55
  rmRegistry: IRenderMimeRegistry,
67
- autocompletionRegistry: IAutocompletionRegistry,
56
+ chatCommandRegistry: IChatCommandRegistry,
68
57
  notebookTracker: INotebookTracker | null,
69
58
  settingsRegistry: ISettingRegistry | null,
70
59
  themeManager: IThemeManager | null
@@ -115,12 +104,30 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
115
104
  });
116
105
 
117
106
  let chatWidget: ReactWidget | null = null;
107
+
108
+ const inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
109
+ const stopButton = stopItem(() => chatHandler.stopStreaming());
110
+ inputToolbarRegistry.addItem('stop', stopButton);
111
+
112
+ chatHandler.writersChanged.connect((_, users) => {
113
+ if (
114
+ users.filter(user => user.username === chatHandler.personaName).length
115
+ ) {
116
+ inputToolbarRegistry.hide('send');
117
+ inputToolbarRegistry.show('stop');
118
+ } else {
119
+ inputToolbarRegistry.hide('stop');
120
+ inputToolbarRegistry.show('send');
121
+ }
122
+ });
123
+
118
124
  try {
119
125
  chatWidget = buildChatSidebar({
120
126
  model: chatHandler,
121
127
  themeManager,
122
128
  rmRegistry,
123
- autocompletionRegistry
129
+ chatCommandRegistry,
130
+ inputToolbarRegistry
124
131
  });
125
132
  chatWidget.title.caption = 'Jupyterlite AI Chat';
126
133
  } catch (e) {
@@ -154,20 +161,28 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
154
161
  id: '@jupyterlite/ai:provider-registry',
155
162
  autoStart: true,
156
163
  requires: [IFormRendererRegistry, ISettingRegistry],
157
- optional: [IRenderMimeRegistry],
164
+ optional: [IRenderMimeRegistry, ISecretsManager, ISettingConnector],
158
165
  provides: IAIProviderRegistry,
159
166
  activate: (
160
167
  app: JupyterFrontEnd,
161
168
  editorRegistry: IFormRendererRegistry,
162
169
  settingRegistry: ISettingRegistry,
163
- rmRegistry?: IRenderMimeRegistry
170
+ rmRegistry?: IRenderMimeRegistry,
171
+ secretsManager?: ISecretsManager,
172
+ settingConnector?: ISettingConnector
164
173
  ): IAIProviderRegistry => {
165
- const providerRegistry = new AIProviderRegistry();
174
+ const providerRegistry = new AIProviderRegistry({ secretsManager });
166
175
 
167
176
  editorRegistry.addRenderer(
168
177
  '@jupyterlite/ai:provider-registry.AIprovider',
169
- aiSettingsRenderer({ providerRegistry, rmRegistry })
178
+ aiSettingsRenderer({
179
+ providerRegistry,
180
+ rmRegistry,
181
+ secretsManager,
182
+ settingConnector
183
+ })
170
184
  );
185
+
171
186
  settingRegistry
172
187
  .load(providerRegistryPlugin.id)
173
188
  .then(settings => {
@@ -176,10 +191,10 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
176
191
  const providerSettings = (settings.get('AIprovider').composite ?? {
177
192
  provider: 'None'
178
193
  }) as ReadonlyPartialJSONObject;
179
- providerRegistry.setProvider(
180
- providerSettings.provider as string,
181
- providerSettings
182
- );
194
+ providerRegistry.setProvider({
195
+ name: providerSettings.provider as string,
196
+ settings: providerSettings
197
+ });
183
198
  };
184
199
 
185
200
  settings.changed.connect(() => updateProvider());
@@ -192,16 +207,29 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
192
207
  );
193
208
  });
194
209
 
195
- // Initialize the registry with the default providers
196
- AIProviders.forEach(provider => providerRegistry.add(provider));
197
-
198
210
  return providerRegistry;
199
211
  }
200
212
  };
201
213
 
214
+ /**
215
+ * Provides the settings connector as a separate plugin to allow for alternative
216
+ * implementations that may want to fetch settings from a different source or
217
+ * endpoint.
218
+ */
219
+ const settingsConnector: JupyterFrontEndPlugin<ISettingConnector> = {
220
+ id: '@jupyterlite/ai:settings-connector',
221
+ description: 'Provides a settings connector which does not save passwords.',
222
+ autoStart: true,
223
+ provides: ISettingConnector,
224
+ activate: (app: JupyterFrontEnd) =>
225
+ new SettingConnector(app.serviceManager.settings)
226
+ };
227
+
202
228
  export default [
203
229
  providerRegistryPlugin,
204
- autocompletionRegistryPlugin,
230
+ chatCommandRegistryPlugin,
205
231
  chatPlugin,
206
- completerPlugin
232
+ completerPlugin,
233
+ settingsConnector,
234
+ ...defaultProviderPlugins
207
235
  ];
package/src/provider.ts CHANGED
@@ -1,12 +1,22 @@
1
- import { ICompletionProviderManager } from '@jupyterlab/completer';
2
1
  import { BaseLanguageModel } from '@langchain/core/language_models/base';
3
2
  import { BaseChatModel } from '@langchain/core/language_models/chat_models';
4
3
  import { ISignal, Signal } from '@lumino/signaling';
5
4
  import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
6
-
7
- import { IBaseCompleter } from './llm-models';
8
- import { IAIProvider, IAIProviderRegistry } from './tokens';
9
5
  import { JSONSchema7 } from 'json-schema';
6
+ import { ISecretsManager } from 'jupyter-secrets-manager';
7
+
8
+ import { IBaseCompleter } from './base-completer';
9
+ import {
10
+ getSecretId,
11
+ SECRETS_NAMESPACE,
12
+ SECRETS_REPLACEMENT
13
+ } from './settings';
14
+ import {
15
+ IAIProvider,
16
+ IAIProviderRegistry,
17
+ IDict,
18
+ ISetProviderOptions
19
+ } from './tokens';
10
20
 
11
21
  export const chatSystemPrompt = (
12
22
  options: AIProviderRegistry.IPromptOptions
@@ -39,6 +49,13 @@ Do not include the prompt in the output, only the string that should be appended
39
49
  `;
40
50
 
41
51
  export class AIProviderRegistry implements IAIProviderRegistry {
52
+ /**
53
+ * The constructor of the provider registry.
54
+ */
55
+ constructor(options: AIProviderRegistry.IOptions) {
56
+ this._secretsManager = options.secretsManager || null;
57
+ }
58
+
42
59
  /**
43
60
  * Get the list of provider names.
44
61
  */
@@ -56,6 +73,11 @@ export class AIProviderRegistry implements IAIProviderRegistry {
56
73
  );
57
74
  }
58
75
  this._providers.set(provider.name, provider);
76
+
77
+ // Set the provider if the loading has been deferred.
78
+ if (provider.name === this._deferredProvider?.name) {
79
+ this.setProvider(this._deferredProvider);
80
+ }
59
81
  }
60
82
 
61
83
  /**
@@ -131,15 +153,36 @@ export class AIProviderRegistry implements IAIProviderRegistry {
131
153
  * Set the providers (chat model and completer).
132
154
  * Creates the providers if the name has changed, otherwise only updates their config.
133
155
  *
134
- * @param name - the name of the provider to use.
135
- * @param settings - the settings for the models.
156
+ * @param options - An object with the name and the settings of the provider to use.
136
157
  */
137
- setProvider(name: string, settings: ReadonlyPartialJSONObject): void {
158
+ async setProvider(options: ISetProviderOptions): Promise<void> {
159
+ const { name, settings } = options;
138
160
  this._currentProvider = this._providers.get(name) ?? null;
161
+ if (this._currentProvider === null) {
162
+ // The current provider may not be loaded when the settings are first loaded.
163
+ // Let's defer the provider loading.
164
+ this._deferredProvider = options;
165
+ } else {
166
+ this._deferredProvider = null;
167
+ }
168
+
169
+ // Build a new settings object containing the secrets.
170
+ const fullSettings: IDict = {};
171
+ for (const key of Object.keys(settings)) {
172
+ if (settings[key] === SECRETS_REPLACEMENT) {
173
+ const id = getSecretId(name, key);
174
+ const secrets = await this._secretsManager?.get(SECRETS_NAMESPACE, id);
175
+ fullSettings[key] = secrets?.value || settings[key];
176
+ continue;
177
+ }
178
+ fullSettings[key] = settings[key];
179
+ }
139
180
 
140
181
  if (this._currentProvider?.completer !== undefined) {
141
182
  try {
142
- this._completer = new this._currentProvider.completer({ ...settings });
183
+ this._completer = new this._currentProvider.completer({
184
+ ...fullSettings
185
+ });
143
186
  this._completerError = '';
144
187
  } catch (e: any) {
145
188
  this._completerError = e.message;
@@ -150,7 +193,9 @@ export class AIProviderRegistry implements IAIProviderRegistry {
150
193
 
151
194
  if (this._currentProvider?.chatModel !== undefined) {
152
195
  try {
153
- this._chatModel = new this._currentProvider.chatModel({ ...settings });
196
+ this._chatModel = new this._currentProvider.chatModel({
197
+ ...fullSettings
198
+ });
154
199
  this._chatError = '';
155
200
  } catch (e: any) {
156
201
  this._chatError = e.message;
@@ -170,6 +215,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
170
215
  return this._providerChanged;
171
216
  }
172
217
 
218
+ private _secretsManager: ISecretsManager | null;
173
219
  private _currentProvider: IAIProvider | null = null;
174
220
  private _completer: IBaseCompleter | null = null;
175
221
  private _chatModel: BaseChatModel | null = null;
@@ -178,6 +224,7 @@ export class AIProviderRegistry implements IAIProviderRegistry {
178
224
  private _chatError: string = '';
179
225
  private _completerError: string = '';
180
226
  private _providers = new Map<string, IAIProvider>();
227
+ private _deferredProvider: ISetProviderOptions | null = null;
181
228
  }
182
229
 
183
230
  export namespace AIProviderRegistry {
@@ -186,13 +233,9 @@ export namespace AIProviderRegistry {
186
233
  */
187
234
  export interface IOptions {
188
235
  /**
189
- * The completion provider manager in which register the LLM completer.
190
- */
191
- completionProviderManager: ICompletionProviderManager;
192
- /**
193
- * The application commands registry.
236
+ * The secrets manager used in the application.
194
237
  */
195
- requestCompletion: () => void;
238
+ secretsManager?: ISecretsManager;
196
239
  }
197
240
 
198
241
  /**
@@ -0,0 +1,3 @@
1
+ export * from './panel';
2
+ export * from './settings-connector';
3
+ export * from './utils';