@jupyterlite/ai 0.3.0 → 0.4.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 (42) hide show
  1. package/lib/chat-handler.d.ts +3 -3
  2. package/lib/chat-handler.js +13 -10
  3. package/lib/completion-provider.d.ts +5 -18
  4. package/lib/completion-provider.js +8 -34
  5. package/lib/index.d.ts +2 -2
  6. package/lib/index.js +44 -42
  7. package/lib/llm-models/index.d.ts +3 -2
  8. package/lib/llm-models/index.js +42 -2
  9. package/lib/provider.d.ts +44 -16
  10. package/lib/provider.js +97 -41
  11. package/lib/settings/instructions.d.ts +2 -0
  12. package/lib/settings/instructions.js +44 -0
  13. package/lib/settings/panel.d.ts +70 -0
  14. package/lib/settings/panel.js +190 -0
  15. package/lib/settings/schemas/base.json +7 -0
  16. package/lib/settings/schemas/index.d.ts +3 -0
  17. package/lib/settings/schemas/index.js +11 -0
  18. package/lib/tokens.d.ts +103 -0
  19. package/lib/tokens.js +5 -0
  20. package/package.json +5 -1
  21. package/schema/provider-registry.json +17 -0
  22. package/src/chat-handler.ts +16 -13
  23. package/src/completion-provider.ts +13 -37
  24. package/src/index.ts +59 -44
  25. package/src/llm-models/index.ts +49 -2
  26. package/src/provider.ts +100 -43
  27. package/src/settings/instructions.ts +48 -0
  28. package/src/settings/panel.tsx +257 -0
  29. package/src/settings/schemas/index.ts +15 -0
  30. package/src/tokens.ts +112 -0
  31. package/style/base.css +4 -0
  32. package/lib/llm-models/utils.d.ts +0 -16
  33. package/lib/llm-models/utils.js +0 -86
  34. package/lib/token.d.ts +0 -13
  35. package/lib/token.js +0 -2
  36. package/schema/ai-provider.json +0 -17
  37. package/src/llm-models/utils.ts +0 -90
  38. package/src/token.ts +0 -19
  39. /package/lib/{_provider-settings/anthropic.json → settings/schemas/_generated/Anthropic.json} +0 -0
  40. /package/lib/{_provider-settings/chromeAI.json → settings/schemas/_generated/ChromeAI.json} +0 -0
  41. /package/lib/{_provider-settings/mistralAI.json → settings/schemas/_generated/MistralAI.json} +0 -0
  42. /package/lib/{_provider-settings/openAI.json → settings/schemas/_generated/OpenAI.json} +0 -0
@@ -0,0 +1,17 @@
1
+ {
2
+ "title": "AI provider",
3
+ "description": "Provider registry settings",
4
+ "jupyter.lab.setting-icon": "@jupyterlite/ai:jupyternaut-lite",
5
+ "jupyter.lab.setting-icon-label": "JupyterLite AI Chat",
6
+ "type": "object",
7
+ "properties": {
8
+ "AIprovider": {
9
+ "type": "object",
10
+ "title": "AI provider",
11
+ "description": "The AI provider configuration",
12
+ "default": {},
13
+ "additionalProperties": true
14
+ }
15
+ },
16
+ "additionalProperties": false
17
+ }
@@ -17,9 +17,8 @@ import {
17
17
  SystemMessage
18
18
  } from '@langchain/core/messages';
19
19
  import { UUID } from '@lumino/coreutils';
20
- import { getErrorMessage } from './llm-models';
21
20
  import { chatSystemPrompt } from './provider';
22
- import { IAIProvider } from './token';
21
+ import { IAIProviderRegistry } from './tokens';
23
22
  import { jupyternautLiteIcon } from './icons';
24
23
 
25
24
  /**
@@ -37,17 +36,21 @@ export type ConnectionMessage = {
37
36
  export class ChatHandler extends ChatModel {
38
37
  constructor(options: ChatHandler.IOptions) {
39
38
  super(options);
40
- this._aiProvider = options.aiProvider;
41
- this._prompt = chatSystemPrompt({ provider_name: this._aiProvider.name });
39
+ this._providerRegistry = options.providerRegistry;
40
+ this._prompt = chatSystemPrompt({
41
+ provider_name: this._providerRegistry.currentName
42
+ });
42
43
 
43
- this._aiProvider.modelChange.connect(() => {
44
- this._errorMessage = this._aiProvider.chatError;
45
- this._prompt = chatSystemPrompt({ provider_name: this._aiProvider.name });
44
+ this._providerRegistry.providerChanged.connect(() => {
45
+ this._errorMessage = this._providerRegistry.chatError;
46
+ this._prompt = chatSystemPrompt({
47
+ provider_name: this._providerRegistry.currentName
48
+ });
46
49
  });
47
50
  }
48
51
 
49
52
  get provider(): BaseChatModel | null {
50
- return this._aiProvider.chatModel;
53
+ return this._providerRegistry.currentChatModel;
51
54
  }
52
55
 
53
56
  /**
@@ -95,7 +98,7 @@ export class ChatHandler extends ChatModel {
95
98
  };
96
99
  this.messageAdded(msg);
97
100
 
98
- if (this._aiProvider.chatModel === null) {
101
+ if (this._providerRegistry.currentChatModel === null) {
99
102
  const errorMsg: IChatMessage = {
100
103
  id: UUID.uuid4(),
101
104
  body: `**${this._errorMessage ? this._errorMessage : this._defaultErrorMessage}**`,
@@ -134,7 +137,7 @@ export class ChatHandler extends ChatModel {
134
137
  let content = '';
135
138
 
136
139
  try {
137
- for await (const chunk of await this._aiProvider.chatModel.stream(
140
+ for await (const chunk of await this._providerRegistry.currentChatModel.stream(
138
141
  messages
139
142
  )) {
140
143
  content += chunk.content ?? chunk;
@@ -144,7 +147,7 @@ export class ChatHandler extends ChatModel {
144
147
  this._history.messages.push(botMsg);
145
148
  return true;
146
149
  } catch (reason) {
147
- const error = getErrorMessage(this._aiProvider.name, reason);
150
+ const error = this._providerRegistry.formatErrorMessage(reason);
148
151
  const errorMsg: IChatMessage = {
149
152
  id: UUID.uuid4(),
150
153
  body: `**${error}**`,
@@ -171,7 +174,7 @@ export class ChatHandler extends ChatModel {
171
174
  super.messageAdded(message);
172
175
  }
173
176
 
174
- private _aiProvider: IAIProvider;
177
+ private _providerRegistry: IAIProviderRegistry;
175
178
  private _personaName = 'AI';
176
179
  private _prompt: string;
177
180
  private _errorMessage: string = '';
@@ -181,6 +184,6 @@ export class ChatHandler extends ChatModel {
181
184
 
182
185
  export namespace ChatHandler {
183
186
  export interface IOptions extends ChatModel.IOptions {
184
- aiProvider: IAIProvider;
187
+ providerRegistry: IAIProviderRegistry;
185
188
  }
186
189
  }
@@ -3,10 +3,9 @@ import {
3
3
  IInlineCompletionContext,
4
4
  IInlineCompletionProvider
5
5
  } from '@jupyterlab/completer';
6
- import { BaseLanguageModel } from '@langchain/core/language_models/base';
7
- import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
8
6
 
9
- import { getCompleter, IBaseCompleter, BaseCompleter } from './llm-models';
7
+ import { IBaseCompleter } from './llm-models';
8
+ import { IAIProviderRegistry } from './tokens';
10
9
 
11
10
  /**
12
11
  * The generic completion provider to register to the completion provider manager.
@@ -15,67 +14,44 @@ export class CompletionProvider implements IInlineCompletionProvider {
15
14
  readonly identifier = '@jupyterlite/ai';
16
15
 
17
16
  constructor(options: CompletionProvider.IOptions) {
18
- const { name, settings } = options;
17
+ this._providerRegistry = options.providerRegistry;
19
18
  this._requestCompletion = options.requestCompletion;
20
- this.setCompleter(name, settings);
21
- }
22
19
 
23
- /**
24
- * Set the completer.
25
- *
26
- * @param name - the name of the completer.
27
- * @param settings - The settings associated to the completer.
28
- */
29
- setCompleter(name: string, settings: ReadonlyPartialJSONObject) {
30
- try {
31
- this._completer = getCompleter(name, settings);
32
- if (this._completer) {
33
- this._completer.requestCompletion = this._requestCompletion;
20
+ this._providerRegistry.providerChanged.connect(() => {
21
+ if (this.completer) {
22
+ this.completer.requestCompletion = this._requestCompletion;
34
23
  }
35
- this._name = this._completer === null ? 'None' : name;
36
- } catch (e: any) {
37
- this._completer = null;
38
- this._name = 'None';
39
- throw e;
40
- }
24
+ });
41
25
  }
42
26
 
43
27
  /**
44
28
  * Get the current completer name.
45
29
  */
46
30
  get name(): string {
47
- return this._name;
31
+ return this._providerRegistry.currentName;
48
32
  }
49
33
 
50
34
  /**
51
35
  * Get the current completer.
52
36
  */
53
37
  get completer(): IBaseCompleter | null {
54
- return this._completer;
55
- }
56
-
57
- /**
58
- * Get the LLM completer.
59
- */
60
- get llmCompleter(): BaseLanguageModel | null {
61
- return this._completer?.provider || null;
38
+ return this._providerRegistry.currentCompleter;
62
39
  }
63
40
 
64
41
  async fetch(
65
42
  request: CompletionHandler.IRequest,
66
43
  context: IInlineCompletionContext
67
44
  ) {
68
- return this._completer?.fetch(request, context);
45
+ return this.completer?.fetch(request, context);
69
46
  }
70
47
 
71
- private _name: string = 'None';
48
+ private _providerRegistry: IAIProviderRegistry;
72
49
  private _requestCompletion: () => void;
73
- private _completer: IBaseCompleter | null = null;
74
50
  }
75
51
 
76
52
  export namespace CompletionProvider {
77
- export interface IOptions extends BaseCompleter.IOptions {
78
- name: string;
53
+ export interface IOptions {
54
+ providerRegistry: IAIProviderRegistry;
79
55
  requestCompletion: () => void;
80
56
  }
81
57
  }
package/src/index.ts CHANGED
@@ -16,12 +16,16 @@ import { ICompletionProviderManager } from '@jupyterlab/completer';
16
16
  import { INotebookTracker } from '@jupyterlab/notebook';
17
17
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
18
18
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
19
+ import { IFormRendererRegistry } from '@jupyterlab/ui-components';
20
+ import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
19
21
 
20
22
  import { ChatHandler } from './chat-handler';
21
- import { getSettings } from './llm-models';
22
- import { AIProvider } from './provider';
23
+ import { CompletionProvider } from './completion-provider';
24
+ import { AIProviders } from './llm-models';
25
+ import { AIProviderRegistry } from './provider';
26
+ import { aiSettingsRenderer } from './settings/panel';
23
27
  import { renderSlashCommandOption } from './slash-commands';
24
- import { IAIProvider } from './token';
28
+ import { IAIProviderRegistry } from './tokens';
25
29
 
26
30
  const autocompletionRegistryPlugin: JupyterFrontEndPlugin<IAutocompletionRegistry> =
27
31
  {
@@ -54,11 +58,11 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
54
58
  id: '@jupyterlite/ai:chat',
55
59
  description: 'LLM chat extension',
56
60
  autoStart: true,
57
- requires: [IAIProvider, IRenderMimeRegistry, IAutocompletionRegistry],
61
+ requires: [IAIProviderRegistry, IRenderMimeRegistry, IAutocompletionRegistry],
58
62
  optional: [INotebookTracker, ISettingRegistry, IThemeManager],
59
63
  activate: async (
60
64
  app: JupyterFrontEnd,
61
- aiProvider: IAIProvider,
65
+ providerRegistry: IAIProviderRegistry,
62
66
  rmRegistry: IRenderMimeRegistry,
63
67
  autocompletionRegistry: IAutocompletionRegistry,
64
68
  notebookTracker: INotebookTracker | null,
@@ -74,8 +78,8 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
74
78
  }
75
79
 
76
80
  const chatHandler = new ChatHandler({
77
- aiProvider: aiProvider,
78
- activeCellManager: activeCellManager
81
+ providerRegistry,
82
+ activeCellManager
79
83
  });
80
84
 
81
85
  let sendWithShiftEnter = false;
@@ -129,50 +133,53 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
129
133
  }
130
134
  };
131
135
 
132
- const aiProviderPlugin: JupyterFrontEndPlugin<IAIProvider> = {
133
- id: '@jupyterlite/ai:ai-provider',
136
+ const completerPlugin: JupyterFrontEndPlugin<void> = {
137
+ id: '@jupyterlite/ai:completer',
134
138
  autoStart: true,
135
- requires: [ICompletionProviderManager, ISettingRegistry],
136
- provides: IAIProvider,
139
+ requires: [IAIProviderRegistry, ICompletionProviderManager],
137
140
  activate: (
138
141
  app: JupyterFrontEnd,
139
- manager: ICompletionProviderManager,
140
- settingRegistry: ISettingRegistry
141
- ): IAIProvider => {
142
- const aiProvider = new AIProvider({
143
- completionProviderManager: manager,
142
+ providerRegistry: IAIProviderRegistry,
143
+ manager: ICompletionProviderManager
144
+ ): void => {
145
+ const completer = new CompletionProvider({
146
+ providerRegistry,
144
147
  requestCompletion: () => app.commands.execute('inline-completer:invoke')
145
148
  });
149
+ manager.registerInlineProvider(completer);
150
+ }
151
+ };
146
152
 
147
- let currentProvider = 'None';
153
+ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
154
+ id: '@jupyterlite/ai:provider-registry',
155
+ autoStart: true,
156
+ requires: [IFormRendererRegistry, ISettingRegistry],
157
+ optional: [IRenderMimeRegistry],
158
+ provides: IAIProviderRegistry,
159
+ activate: (
160
+ app: JupyterFrontEnd,
161
+ editorRegistry: IFormRendererRegistry,
162
+ settingRegistry: ISettingRegistry,
163
+ rmRegistry?: IRenderMimeRegistry
164
+ ): IAIProviderRegistry => {
165
+ const providerRegistry = new AIProviderRegistry();
166
+
167
+ editorRegistry.addRenderer(
168
+ '@jupyterlite/ai:provider-registry.AIprovider',
169
+ aiSettingsRenderer({ providerRegistry, rmRegistry })
170
+ );
148
171
  settingRegistry
149
- .load(aiProviderPlugin.id)
172
+ .load(providerRegistryPlugin.id)
150
173
  .then(settings => {
151
174
  const updateProvider = () => {
152
- const provider = settings.get('provider').composite as string;
153
- if (provider !== currentProvider) {
154
- // Update the settings panel.
155
- currentProvider = provider;
156
- const settingsProperties = settings.schema.properties;
157
- if (settingsProperties) {
158
- const schemaKeys = Object.keys(settingsProperties);
159
- schemaKeys.forEach(key => {
160
- if (key !== 'provider') {
161
- delete settings.schema.properties?.[key];
162
- }
163
- });
164
- const properties = getSettings(provider);
165
- if (properties === null) {
166
- return;
167
- }
168
- Object.entries(properties).forEach(([name, value], index) => {
169
- settingsProperties[name] = value as ISettingRegistry.IProperty;
170
- });
171
- }
172
- }
173
-
174
175
  // Update the settings to the AI providers.
175
- aiProvider.setModels(provider, settings.composite);
176
+ const providerSettings = (settings.get('AIprovider').composite ?? {
177
+ provider: 'None'
178
+ }) as ReadonlyPartialJSONObject;
179
+ providerRegistry.setProvider(
180
+ providerSettings.provider as string,
181
+ providerSettings
182
+ );
176
183
  };
177
184
 
178
185
  settings.changed.connect(() => updateProvider());
@@ -180,13 +187,21 @@ const aiProviderPlugin: JupyterFrontEndPlugin<IAIProvider> = {
180
187
  })
181
188
  .catch(reason => {
182
189
  console.error(
183
- `Failed to load settings for ${aiProviderPlugin.id}`,
190
+ `Failed to load settings for ${providerRegistryPlugin.id}`,
184
191
  reason
185
192
  );
186
193
  });
187
194
 
188
- return aiProvider;
195
+ // Initialize the registry with the default providers
196
+ AIProviders.forEach(provider => providerRegistry.add(provider));
197
+
198
+ return providerRegistry;
189
199
  }
190
200
  };
191
201
 
192
- export default [chatPlugin, autocompletionRegistryPlugin, aiProviderPlugin];
202
+ export default [
203
+ providerRegistryPlugin,
204
+ autocompletionRegistryPlugin,
205
+ chatPlugin,
206
+ completerPlugin
207
+ ];
@@ -1,3 +1,50 @@
1
+ import { ChatAnthropic } from '@langchain/anthropic';
2
+ import { ChromeAI } from '@langchain/community/experimental/llms/chrome_ai';
3
+ import { ChatMistralAI } from '@langchain/mistralai';
4
+ import { ChatOpenAI } from '@langchain/openai';
5
+
6
+ import { AnthropicCompleter } from './anthropic-completer';
7
+ import { CodestralCompleter } from './codestral-completer';
8
+ import { ChromeCompleter } from './chrome-completer';
9
+ import { OpenAICompleter } from './openai-completer';
10
+
11
+ import { instructions } from '../settings/instructions';
12
+ import { ProviderSettings } from '../settings/schemas';
13
+
14
+ import { IAIProvider } from '../tokens';
15
+
1
16
  export * from './base-completer';
2
- export * from './codestral-completer';
3
- export * from './utils';
17
+
18
+ const AIProviders: IAIProvider[] = [
19
+ {
20
+ name: 'Anthropic',
21
+ chatModel: ChatAnthropic,
22
+ completer: AnthropicCompleter,
23
+ settingsSchema: ProviderSettings.Anthropic,
24
+ errorMessage: (error: any) => error.error.error.message
25
+ },
26
+ {
27
+ name: 'ChromeAI',
28
+ // TODO: fix
29
+ // @ts-expect-error: missing properties
30
+ chatModel: ChromeAI,
31
+ completer: ChromeCompleter,
32
+ instructions: instructions.ChromeAI,
33
+ settingsSchema: ProviderSettings.ChromeAI
34
+ },
35
+ {
36
+ name: 'MistralAI',
37
+ chatModel: ChatMistralAI,
38
+ completer: CodestralCompleter,
39
+ instructions: instructions.MistralAI,
40
+ settingsSchema: ProviderSettings.MistralAI
41
+ },
42
+ {
43
+ name: 'OpenAI',
44
+ chatModel: ChatOpenAI,
45
+ completer: OpenAICompleter,
46
+ settingsSchema: ProviderSettings.OpenAI
47
+ }
48
+ ];
49
+
50
+ export { AIProviders };
package/src/provider.ts CHANGED
@@ -4,11 +4,13 @@ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
4
4
  import { ISignal, Signal } from '@lumino/signaling';
5
5
  import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
6
6
 
7
- import { CompletionProvider } from './completion-provider';
8
- import { getChatModel, IBaseCompleter } from './llm-models';
9
- import { IAIProvider } from './token';
7
+ import { IBaseCompleter } from './llm-models';
8
+ import { IAIProvider, IAIProviderRegistry } from './tokens';
9
+ import { JSONSchema7 } from 'json-schema';
10
10
 
11
- export const chatSystemPrompt = (options: AIProvider.IPromptOptions) => `
11
+ export const chatSystemPrompt = (
12
+ options: AIProviderRegistry.IPromptOptions
13
+ ) => `
12
14
  You are Jupyternaut, a conversational assistant living in JupyterLab to help users.
13
15
  You are not a language model, but rather an application built on a foundation model from ${options.provider_name}.
14
16
  You are talkative and you provide lots of specific details from the foundation model's context.
@@ -36,40 +38,79 @@ would write.
36
38
  Do not include the prompt in the output, only the string that should be appended to the current input.
37
39
  `;
38
40
 
39
- export class AIProvider implements IAIProvider {
40
- constructor(options: AIProvider.IOptions) {
41
- this._completionProvider = new CompletionProvider({
42
- name: 'None',
43
- settings: {},
44
- requestCompletion: options.requestCompletion
45
- });
46
- options.completionProviderManager.registerInlineProvider(
47
- this._completionProvider
48
- );
41
+ export class AIProviderRegistry implements IAIProviderRegistry {
42
+ /**
43
+ * Get the list of provider names.
44
+ */
45
+ get providers(): string[] {
46
+ return Array.from(this._providers.keys());
47
+ }
48
+
49
+ /**
50
+ * Add a new provider.
51
+ */
52
+ add(provider: IAIProvider): void {
53
+ if (this._providers.has(provider.name)) {
54
+ throw new Error(
55
+ `A AI provider named '${provider.name}' is already registered`
56
+ );
57
+ }
58
+ this._providers.set(provider.name, provider);
49
59
  }
50
60
 
51
- get name(): string {
61
+ /**
62
+ * Get the current provider name.
63
+ */
64
+ get currentName(): string {
52
65
  return this._name;
53
66
  }
54
67
 
55
68
  /**
56
69
  * Get the current completer of the completion provider.
57
70
  */
58
- get completer(): IBaseCompleter | null {
59
- if (this._name === null) {
71
+ get currentCompleter(): IBaseCompleter | null {
72
+ if (this._name === 'None') {
60
73
  return null;
61
74
  }
62
- return this._completionProvider.completer;
75
+ return this._completer;
63
76
  }
64
77
 
65
78
  /**
66
79
  * Get the current llm chat model.
67
80
  */
68
- get chatModel(): BaseChatModel | null {
69
- if (this._name === null) {
81
+ get currentChatModel(): BaseChatModel | null {
82
+ if (this._name === 'None') {
70
83
  return null;
71
84
  }
72
- return this._llmChatModel;
85
+ return this._chatModel;
86
+ }
87
+
88
+ /**
89
+ * Get the settings schema of a given provider.
90
+ */
91
+ getSettingsSchema(provider: string): JSONSchema7 {
92
+ return (this._providers.get(provider)?.settingsSchema?.properties ||
93
+ {}) as JSONSchema7;
94
+ }
95
+
96
+ /**
97
+ * Get the instructions of a given provider.
98
+ */
99
+ getInstructions(provider: string): string | undefined {
100
+ return this._providers.get(provider)?.instructions;
101
+ }
102
+
103
+ /**
104
+ * Format an error message from the current provider.
105
+ */
106
+ formatErrorMessage(error: any): string {
107
+ if (this._currentProvider?.errorMessage) {
108
+ return this._currentProvider?.errorMessage(error);
109
+ }
110
+ if (error.message) {
111
+ return error.message;
112
+ }
113
+ return error;
73
114
  }
74
115
 
75
116
  /**
@@ -87,43 +128,59 @@ export class AIProvider implements IAIProvider {
87
128
  }
88
129
 
89
130
  /**
90
- * Set the models (chat model and completer).
91
- * Creates the models if the name has changed, otherwise only updates their config.
131
+ * Set the providers (chat model and completer).
132
+ * Creates the providers if the name has changed, otherwise only updates their config.
92
133
  *
93
- * @param name - the name of the model to use.
134
+ * @param name - the name of the provider to use.
94
135
  * @param settings - the settings for the models.
95
136
  */
96
- setModels(name: string, settings: ReadonlyPartialJSONObject) {
97
- try {
98
- this._completionProvider.setCompleter(name, settings);
99
- this._completerError = '';
100
- } catch (e: any) {
101
- this._completerError = e.message;
137
+ setProvider(name: string, settings: ReadonlyPartialJSONObject): void {
138
+ this._currentProvider = this._providers.get(name) ?? null;
139
+
140
+ if (this._currentProvider?.completer !== undefined) {
141
+ try {
142
+ this._completer = new this._currentProvider.completer({ ...settings });
143
+ this._completerError = '';
144
+ } catch (e: any) {
145
+ this._completerError = e.message;
146
+ }
147
+ } else {
148
+ this._completer = null;
102
149
  }
103
- try {
104
- this._llmChatModel = getChatModel(name, settings);
105
- this._chatError = '';
106
- } catch (e: any) {
107
- this._chatError = e.message;
108
- this._llmChatModel = null;
150
+
151
+ if (this._currentProvider?.chatModel !== undefined) {
152
+ try {
153
+ this._chatModel = new this._currentProvider.chatModel({ ...settings });
154
+ this._chatError = '';
155
+ } catch (e: any) {
156
+ this._chatError = e.message;
157
+ this._chatModel = null;
158
+ }
159
+ } else {
160
+ this._chatModel = null;
109
161
  }
110
162
  this._name = name;
111
- this._modelChange.emit();
163
+ this._providerChanged.emit();
112
164
  }
113
165
 
114
- get modelChange(): ISignal<IAIProvider, void> {
115
- return this._modelChange;
166
+ /**
167
+ * A signal emitting when the provider or its settings has changed.
168
+ */
169
+ get providerChanged(): ISignal<IAIProviderRegistry, void> {
170
+ return this._providerChanged;
116
171
  }
117
172
 
118
- private _completionProvider: CompletionProvider;
119
- private _llmChatModel: BaseChatModel | null = null;
173
+ private _currentProvider: IAIProvider | null = null;
174
+ private _completer: IBaseCompleter | null = null;
175
+ private _chatModel: BaseChatModel | null = null;
120
176
  private _name: string = 'None';
121
- private _modelChange = new Signal<IAIProvider, void>(this);
177
+ private _providerChanged = new Signal<IAIProviderRegistry, void>(this);
122
178
  private _chatError: string = '';
123
179
  private _completerError: string = '';
180
+ private _providers = new Map<string, IAIProvider>();
124
181
  }
125
182
 
126
- export namespace AIProvider {
183
+ export namespace AIProviderRegistry {
127
184
  /**
128
185
  * The options for the LLM provider.
129
186
  */
@@ -0,0 +1,48 @@
1
+ import { IDict } from '../tokens';
2
+
3
+ const chromeAiInstructions = `
4
+ <i class="fas fa-exclamation-triangle"></i> Support for ChromeAI is still experimental and only available in Google Chrome.
5
+
6
+ You can test ChromeAI is enabled in your browser by going to the following URL: https://chromeai.org/
7
+
8
+ Enable the proper flags in Google Chrome.
9
+
10
+ - chrome://flags/#prompt-api-for-gemini-nano
11
+ - Select: \`Enabled\`
12
+ - chrome://flags/#optimization-guide-on-device-model
13
+ - Select: \`Enabled BypassPrefRequirement\`
14
+ - chrome://components
15
+ - Click \`Check for Update\` on Optimization Guide On Device Model to download the model
16
+ - [Optional] chrome://flags/#text-safety-classifier
17
+
18
+ <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">
19
+
20
+ Then restart Chrome for these changes to take effect.
21
+
22
+ <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).
23
+ During the download, ChromeAI may not be available via the extension.
24
+
25
+ <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
26
+ `;
27
+
28
+ const mistralAIInstructions = `
29
+ <i class="fas fa-exclamation-triangle"></i> This extension is still very much experimental. It is not an official MistralAI extension.
30
+
31
+ 1. Go to https://console.mistral.ai/api-keys/ and create an API key.
32
+
33
+ <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">
34
+
35
+ 2. Open the JupyterLab settings and go to the **Ai providers** section to select the \`MistralAI\`
36
+ provider and the API key (required).
37
+
38
+ <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">
39
+
40
+ 3. Open the chat, or use the inline completer
41
+
42
+ <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">
43
+ `;
44
+
45
+ export const instructions: IDict = {
46
+ ChromeAI: chromeAiInstructions,
47
+ MistralAI: mistralAIInstructions
48
+ };