@jupyterlite/ai 0.4.0 → 0.5.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.
- package/lib/chat-handler.d.ts +7 -1
- package/lib/chat-handler.js +29 -0
- package/lib/completion-provider.d.ts +1 -1
- package/lib/{llm-models/anthropic-completer.d.ts → default-providers/Anthropic/completer.d.ts} +1 -1
- package/lib/{llm-models/anthropic-completer.js → default-providers/Anthropic/completer.js} +1 -1
- package/lib/{llm-models/chrome-completer.d.ts → default-providers/ChromeAI/completer.d.ts} +1 -1
- package/lib/{llm-models/chrome-completer.js → default-providers/ChromeAI/completer.js} +1 -1
- package/lib/default-providers/ChromeAI/instructions.d.ts +2 -0
- package/lib/default-providers/ChromeAI/instructions.js +24 -0
- package/lib/{llm-models/codestral-completer.d.ts → default-providers/MistralAI/completer.d.ts} +1 -1
- package/lib/{llm-models/codestral-completer.js → default-providers/MistralAI/completer.js} +1 -1
- package/lib/default-providers/MistralAI/instructions.d.ts +2 -0
- package/lib/default-providers/MistralAI/instructions.js +16 -0
- package/lib/{llm-models/openai-completer.d.ts → default-providers/OpenAI/completer.d.ts} +1 -1
- package/lib/{llm-models/openai-completer.js → default-providers/OpenAI/completer.js} +1 -1
- package/lib/default-providers/index.d.ts +2 -0
- package/lib/default-providers/index.js +60 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +17 -32
- package/lib/provider.d.ts +1 -1
- package/lib/settings/panel.d.ts +14 -0
- package/lib/settings/panel.js +82 -5
- package/lib/tokens.d.ts +1 -1
- package/package.json +8 -5
- package/schema/provider-registry.json +6 -0
- package/src/chat-handler.ts +34 -0
- package/src/completion-provider.ts +1 -1
- package/src/{llm-models/anthropic-completer.ts → default-providers/Anthropic/completer.ts} +2 -2
- package/src/{llm-models/chrome-completer.ts → default-providers/ChromeAI/completer.ts} +3 -2
- package/src/default-providers/ChromeAI/instructions.ts +24 -0
- package/src/{llm-models/codestral-completer.ts → default-providers/MistralAI/completer.ts} +2 -2
- package/src/default-providers/MistralAI/instructions.ts +16 -0
- package/src/{llm-models/openai-completer.ts → default-providers/OpenAI/completer.ts} +2 -2
- package/src/default-providers/index.ts +71 -0
- package/src/index.ts +25 -42
- package/src/provider.ts +1 -1
- package/src/settings/panel.tsx +93 -4
- package/src/tokens.ts +1 -1
- package/lib/llm-models/index.d.ts +0 -4
- package/lib/llm-models/index.js +0 -43
- package/lib/settings/instructions.d.ts +0 -2
- package/lib/settings/instructions.js +0 -44
- package/lib/settings/schemas/index.d.ts +0 -3
- package/lib/settings/schemas/index.js +0 -11
- package/lib/slash-commands.d.ts +0 -16
- package/lib/slash-commands.js +0 -25
- package/src/llm-models/index.ts +0 -50
- package/src/settings/instructions.ts +0 -48
- package/src/settings/schemas/index.ts +0 -15
- package/src/slash-commands.tsx +0 -55
- /package/lib/{llm-models/base-completer.d.ts → base-completer.d.ts} +0 -0
- /package/lib/{llm-models/base-completer.js → base-completer.js} +0 -0
- /package/lib/{settings/schemas/_generated/Anthropic.json → default-providers/Anthropic/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/ChromeAI.json → default-providers/ChromeAI/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/MistralAI.json → default-providers/MistralAI/settings-schema.json} +0 -0
- /package/lib/{settings/schemas/_generated/OpenAI.json → default-providers/OpenAI/settings-schema.json} +0 -0
- /package/lib/settings/{schemas/base.json → base.json} +0 -0
- /package/src/{llm-models/base-completer.ts → base-completer.ts} +0 -0
- /package/src/{llm-models/svg.d.ts → global.d.ts} +0 -0
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
"jupyter.lab.setting-icon-label": "JupyterLite AI Chat",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
|
+
"UseSecretsManager": {
|
|
9
|
+
"type": "boolean",
|
|
10
|
+
"title": "Use secrets manager",
|
|
11
|
+
"description": "Whether to use or not the secrets manager. If not, secrets will be stored in the browser (local storage)",
|
|
12
|
+
"default": true
|
|
13
|
+
},
|
|
8
14
|
"AIprovider": {
|
|
9
15
|
"type": "object",
|
|
10
16
|
"title": "AI provider",
|
package/src/chat-handler.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
ChatCommand,
|
|
7
8
|
ChatModel,
|
|
9
|
+
IChatCommandProvider,
|
|
8
10
|
IChatHistory,
|
|
9
11
|
IChatMessage,
|
|
12
|
+
IInputModel,
|
|
10
13
|
INewMessage
|
|
11
14
|
} from '@jupyter/chat';
|
|
12
15
|
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
@@ -186,4 +189,35 @@ export namespace ChatHandler {
|
|
|
186
189
|
export interface IOptions extends ChatModel.IOptions {
|
|
187
190
|
providerRegistry: IAIProviderRegistry;
|
|
188
191
|
}
|
|
192
|
+
|
|
193
|
+
export class ClearCommandProvider implements IChatCommandProvider {
|
|
194
|
+
public id: string = '@jupyterlite/ai:clear-commands';
|
|
195
|
+
private _slash_commands: ChatCommand[] = [
|
|
196
|
+
{
|
|
197
|
+
name: '/clear',
|
|
198
|
+
providerId: this.id,
|
|
199
|
+
replaceWith: '/clear',
|
|
200
|
+
description: 'Clear the chat'
|
|
201
|
+
}
|
|
202
|
+
];
|
|
203
|
+
async getChatCommands(inputModel: IInputModel) {
|
|
204
|
+
const match = inputModel.currentWord?.match(/^\/\w*/)?.[0];
|
|
205
|
+
if (!match) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const commands = this._slash_commands.filter(cmd =>
|
|
210
|
+
cmd.name.startsWith(match)
|
|
211
|
+
);
|
|
212
|
+
return commands;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async handleChatCommand(
|
|
216
|
+
command: ChatCommand,
|
|
217
|
+
inputModel: IInputModel
|
|
218
|
+
): Promise<void> {
|
|
219
|
+
// no handling needed because `replaceWith` is set in each command.
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
189
223
|
}
|
|
@@ -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 '
|
|
10
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
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
|
-
|
|
9
|
-
import {
|
|
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 '
|
|
15
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
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 '
|
|
10
|
-
import { COMPLETION_SYSTEM_PROMPT } from '
|
|
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,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ActiveCellManager,
|
|
3
|
-
AutocompletionRegistry,
|
|
4
3
|
buildChatSidebar,
|
|
5
4
|
buildErrorWidget,
|
|
5
|
+
ChatCommandRegistry,
|
|
6
6
|
IActiveCellManager,
|
|
7
|
-
|
|
8
|
-
IAutocompletionRegistry
|
|
7
|
+
IChatCommandRegistry
|
|
9
8
|
} from '@jupyter/chat';
|
|
10
9
|
import {
|
|
11
10
|
JupyterFrontEnd,
|
|
@@ -18,53 +17,38 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
|
18
17
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
19
18
|
import { IFormRendererRegistry } from '@jupyterlab/ui-components';
|
|
20
19
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
20
|
+
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
21
21
|
|
|
22
22
|
import { ChatHandler } from './chat-handler';
|
|
23
23
|
import { CompletionProvider } from './completion-provider';
|
|
24
|
-
import {
|
|
24
|
+
import { defaultProviderPlugins } from './default-providers';
|
|
25
25
|
import { AIProviderRegistry } from './provider';
|
|
26
26
|
import { aiSettingsRenderer } from './settings/panel';
|
|
27
|
-
import { renderSlashCommandOption } from './slash-commands';
|
|
28
27
|
import { IAIProviderRegistry } from './tokens';
|
|
29
28
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
};
|
|
29
|
+
const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
|
|
30
|
+
id: '@jupyterlite/ai:autocompletion-registry',
|
|
31
|
+
description: 'Autocompletion registry',
|
|
32
|
+
autoStart: true,
|
|
33
|
+
provides: IChatCommandRegistry,
|
|
34
|
+
activate: () => {
|
|
35
|
+
const registry = new ChatCommandRegistry();
|
|
36
|
+
registry.addProvider(new ChatHandler.ClearCommandProvider());
|
|
37
|
+
return registry;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
56
40
|
|
|
57
41
|
const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
58
42
|
id: '@jupyterlite/ai:chat',
|
|
59
43
|
description: 'LLM chat extension',
|
|
60
44
|
autoStart: true,
|
|
61
|
-
requires: [IAIProviderRegistry, IRenderMimeRegistry,
|
|
45
|
+
requires: [IAIProviderRegistry, IRenderMimeRegistry, IChatCommandRegistry],
|
|
62
46
|
optional: [INotebookTracker, ISettingRegistry, IThemeManager],
|
|
63
47
|
activate: async (
|
|
64
48
|
app: JupyterFrontEnd,
|
|
65
49
|
providerRegistry: IAIProviderRegistry,
|
|
66
50
|
rmRegistry: IRenderMimeRegistry,
|
|
67
|
-
|
|
51
|
+
chatCommandRegistry: IChatCommandRegistry,
|
|
68
52
|
notebookTracker: INotebookTracker | null,
|
|
69
53
|
settingsRegistry: ISettingRegistry | null,
|
|
70
54
|
themeManager: IThemeManager | null
|
|
@@ -120,7 +104,7 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
|
|
|
120
104
|
model: chatHandler,
|
|
121
105
|
themeManager,
|
|
122
106
|
rmRegistry,
|
|
123
|
-
|
|
107
|
+
chatCommandRegistry
|
|
124
108
|
});
|
|
125
109
|
chatWidget.title.caption = 'Jupyterlite AI Chat';
|
|
126
110
|
} catch (e) {
|
|
@@ -154,19 +138,20 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
|
154
138
|
id: '@jupyterlite/ai:provider-registry',
|
|
155
139
|
autoStart: true,
|
|
156
140
|
requires: [IFormRendererRegistry, ISettingRegistry],
|
|
157
|
-
optional: [IRenderMimeRegistry],
|
|
141
|
+
optional: [IRenderMimeRegistry, ISecretsManager],
|
|
158
142
|
provides: IAIProviderRegistry,
|
|
159
143
|
activate: (
|
|
160
144
|
app: JupyterFrontEnd,
|
|
161
145
|
editorRegistry: IFormRendererRegistry,
|
|
162
146
|
settingRegistry: ISettingRegistry,
|
|
163
|
-
rmRegistry?: IRenderMimeRegistry
|
|
147
|
+
rmRegistry?: IRenderMimeRegistry,
|
|
148
|
+
secretsManager?: ISecretsManager
|
|
164
149
|
): IAIProviderRegistry => {
|
|
165
150
|
const providerRegistry = new AIProviderRegistry();
|
|
166
151
|
|
|
167
152
|
editorRegistry.addRenderer(
|
|
168
153
|
'@jupyterlite/ai:provider-registry.AIprovider',
|
|
169
|
-
aiSettingsRenderer({ providerRegistry, rmRegistry })
|
|
154
|
+
aiSettingsRenderer({ providerRegistry, rmRegistry, secretsManager })
|
|
170
155
|
);
|
|
171
156
|
settingRegistry
|
|
172
157
|
.load(providerRegistryPlugin.id)
|
|
@@ -192,16 +177,14 @@ const providerRegistryPlugin: JupyterFrontEndPlugin<IAIProviderRegistry> = {
|
|
|
192
177
|
);
|
|
193
178
|
});
|
|
194
179
|
|
|
195
|
-
// Initialize the registry with the default providers
|
|
196
|
-
AIProviders.forEach(provider => providerRegistry.add(provider));
|
|
197
|
-
|
|
198
180
|
return providerRegistry;
|
|
199
181
|
}
|
|
200
182
|
};
|
|
201
183
|
|
|
202
184
|
export default [
|
|
203
185
|
providerRegistryPlugin,
|
|
204
|
-
|
|
186
|
+
chatCommandRegistryPlugin,
|
|
205
187
|
chatPlugin,
|
|
206
|
-
completerPlugin
|
|
188
|
+
completerPlugin,
|
|
189
|
+
...defaultProviderPlugins
|
|
207
190
|
];
|
package/src/provider.ts
CHANGED
|
@@ -4,7 +4,7 @@ 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 { IBaseCompleter } from './
|
|
7
|
+
import { IBaseCompleter } from './base-completer';
|
|
8
8
|
import { IAIProvider, IAIProviderRegistry } from './tokens';
|
|
9
9
|
import { JSONSchema7 } from 'json-schema';
|
|
10
10
|
|
package/src/settings/panel.tsx
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
2
|
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
3
3
|
import { FormComponent, IFormRenderer } from '@jupyterlab/ui-components';
|
|
4
|
+
import { ArrayExt } from '@lumino/algorithm';
|
|
4
5
|
import { JSONExt } from '@lumino/coreutils';
|
|
5
6
|
import { IChangeEvent } from '@rjsf/core';
|
|
6
7
|
import type { FieldProps } from '@rjsf/utils';
|
|
7
8
|
import validator from '@rjsf/validator-ajv8';
|
|
8
9
|
import { JSONSchema7 } from 'json-schema';
|
|
10
|
+
import { ISecretsManager } from 'jupyter-secrets-manager';
|
|
9
11
|
import React from 'react';
|
|
10
12
|
|
|
11
|
-
import baseSettings from './
|
|
13
|
+
import baseSettings from './base.json';
|
|
12
14
|
import { IAIProviderRegistry, IDict } from '../tokens';
|
|
13
15
|
|
|
16
|
+
const SECRETS_NAMESPACE = '@jupyterlite/ai';
|
|
14
17
|
const MD_MIME_TYPE = 'text/markdown';
|
|
15
18
|
const STORAGE_NAME = '@jupyterlite/ai:settings';
|
|
16
19
|
const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
@@ -18,6 +21,7 @@ const INSTRUCTION_CLASS = 'jp-AISettingsInstructions';
|
|
|
18
21
|
export const aiSettingsRenderer = (options: {
|
|
19
22
|
providerRegistry: IAIProviderRegistry;
|
|
20
23
|
rmRegistry?: IRenderMimeRegistry;
|
|
24
|
+
secretsManager?: ISecretsManager;
|
|
21
25
|
}): IFormRenderer => {
|
|
22
26
|
return {
|
|
23
27
|
fieldRenderer: (props: FieldProps) => {
|
|
@@ -49,8 +53,12 @@ export class AiSettings extends React.Component<
|
|
|
49
53
|
}
|
|
50
54
|
this._providerRegistry = props.formContext.providerRegistry;
|
|
51
55
|
this._rmRegistry = props.formContext.rmRegistry ?? null;
|
|
56
|
+
this._secretsManager = props.formContext.secretsManager ?? null;
|
|
52
57
|
this._settings = props.formContext.settings;
|
|
53
58
|
|
|
59
|
+
this._useSecretsManager =
|
|
60
|
+
(this._settings.get('UseSecretsManager').composite as boolean) ?? true;
|
|
61
|
+
|
|
54
62
|
// Initialize the providers schema.
|
|
55
63
|
const providerSchema = JSONExt.deepCopy(baseSettings) as any;
|
|
56
64
|
providerSchema.properties.provider = {
|
|
@@ -95,6 +103,44 @@ export class AiSettings extends React.Component<
|
|
|
95
103
|
this._settings
|
|
96
104
|
.set('AIprovider', this._currentSettings)
|
|
97
105
|
.catch(console.error);
|
|
106
|
+
|
|
107
|
+
this._settings.changed.connect(() => {
|
|
108
|
+
const useSecretsManager =
|
|
109
|
+
(this._settings.get('UseSecretsManager').composite as boolean) ?? true;
|
|
110
|
+
if (useSecretsManager !== this._useSecretsManager) {
|
|
111
|
+
this.updateUseSecretsManager(useSecretsManager);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async componentDidUpdate(): Promise<void> {
|
|
117
|
+
if (!this._secretsManager || !this._useSecretsManager) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Attach the password inputs to the secrets manager only if they have changed.
|
|
121
|
+
const inputs = this._formRef.current?.getElementsByTagName('input') || [];
|
|
122
|
+
if (ArrayExt.shallowEqual(inputs, this._formInputs)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await this._secretsManager.detachAll(SECRETS_NAMESPACE);
|
|
127
|
+
this._formInputs = [...inputs];
|
|
128
|
+
this._unsavedFields = [];
|
|
129
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
130
|
+
if (inputs[i].type.toLowerCase() === 'password') {
|
|
131
|
+
const label = inputs[i].getAttribute('label');
|
|
132
|
+
if (label) {
|
|
133
|
+
const id = `${this._provider}-${label}`;
|
|
134
|
+
this._secretsManager.attach(
|
|
135
|
+
SECRETS_NAMESPACE,
|
|
136
|
+
id,
|
|
137
|
+
inputs[i],
|
|
138
|
+
(value: string) => this._onPasswordUpdated(label, value)
|
|
139
|
+
);
|
|
140
|
+
this._unsavedFields.push(label);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
98
144
|
}
|
|
99
145
|
|
|
100
146
|
/**
|
|
@@ -126,11 +172,38 @@ export class AiSettings extends React.Component<
|
|
|
126
172
|
* Save settings in local storage for a given provider.
|
|
127
173
|
*/
|
|
128
174
|
saveSettings(value: IDict<any>) {
|
|
175
|
+
const currentSettings = { ...value };
|
|
129
176
|
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) ?? '{}');
|
|
130
|
-
|
|
177
|
+
this._unsavedFields.forEach(field => delete currentSettings[field]);
|
|
178
|
+
settings[this._provider] = currentSettings;
|
|
131
179
|
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
132
180
|
}
|
|
133
181
|
|
|
182
|
+
private updateUseSecretsManager = (value: boolean) => {
|
|
183
|
+
this._useSecretsManager = value;
|
|
184
|
+
if (!value) {
|
|
185
|
+
// Detach all the password inputs attached to the secrets manager, and save the
|
|
186
|
+
// current settings to the local storage to save the password.
|
|
187
|
+
this._secretsManager?.detachAll(SECRETS_NAMESPACE);
|
|
188
|
+
this._formInputs = [];
|
|
189
|
+
this._unsavedFields = [];
|
|
190
|
+
this.saveSettings(this._currentSettings);
|
|
191
|
+
} else {
|
|
192
|
+
// Remove all the keys stored locally and attach the password inputs to the
|
|
193
|
+
// secrets manager.
|
|
194
|
+
const settings = JSON.parse(localStorage.getItem(STORAGE_NAME) || '{}');
|
|
195
|
+
Object.keys(settings).forEach(provider => {
|
|
196
|
+
Object.keys(settings[provider])
|
|
197
|
+
.filter(key => key.toLowerCase().includes('key'))
|
|
198
|
+
.forEach(key => {
|
|
199
|
+
delete settings[provider][key];
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
localStorage.setItem(STORAGE_NAME, JSON.stringify(settings));
|
|
203
|
+
this.componentDidUpdate();
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
134
207
|
/**
|
|
135
208
|
* Update the UI schema of the form.
|
|
136
209
|
* Currently use to hide API keys.
|
|
@@ -206,6 +279,17 @@ export class AiSettings extends React.Component<
|
|
|
206
279
|
.catch(console.error);
|
|
207
280
|
};
|
|
208
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Callback function called when the password input has been programmatically updated
|
|
284
|
+
* with the secret manager.
|
|
285
|
+
*/
|
|
286
|
+
private _onPasswordUpdated = (fieldName: string, value: string) => {
|
|
287
|
+
this._currentSettings[fieldName] = value;
|
|
288
|
+
this._settings
|
|
289
|
+
.set('AIprovider', { provider: this._provider, ...this._currentSettings })
|
|
290
|
+
.catch(console.error);
|
|
291
|
+
};
|
|
292
|
+
|
|
209
293
|
/**
|
|
210
294
|
* Triggered when the form value has changed, to update the current settings and save
|
|
211
295
|
* it in local storage.
|
|
@@ -221,7 +305,7 @@ export class AiSettings extends React.Component<
|
|
|
221
305
|
|
|
222
306
|
render(): JSX.Element {
|
|
223
307
|
return (
|
|
224
|
-
|
|
308
|
+
<div ref={this._formRef}>
|
|
225
309
|
<WrappedFormComponent
|
|
226
310
|
formData={{ provider: this._provider }}
|
|
227
311
|
schema={this._providerSchema}
|
|
@@ -243,15 +327,20 @@ export class AiSettings extends React.Component<
|
|
|
243
327
|
onChange={this._onFormChange}
|
|
244
328
|
uiSchema={this._uiSchema}
|
|
245
329
|
/>
|
|
246
|
-
|
|
330
|
+
</div>
|
|
247
331
|
);
|
|
248
332
|
}
|
|
249
333
|
|
|
250
334
|
private _providerRegistry: IAIProviderRegistry;
|
|
251
335
|
private _provider: string;
|
|
252
336
|
private _providerSchema: JSONSchema7;
|
|
337
|
+
private _useSecretsManager: boolean;
|
|
253
338
|
private _rmRegistry: IRenderMimeRegistry | null;
|
|
339
|
+
private _secretsManager: ISecretsManager | null;
|
|
254
340
|
private _currentSettings: IDict<any> = { provider: 'None' };
|
|
255
341
|
private _uiSchema: IDict<any> = {};
|
|
256
342
|
private _settings: ISettingRegistry.ISettings;
|
|
343
|
+
private _formRef = React.createRef<HTMLDivElement>();
|
|
344
|
+
private _unsavedFields: string[] = [];
|
|
345
|
+
private _formInputs: HTMLInputElement[] = [];
|
|
257
346
|
}
|
package/src/tokens.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ReadonlyPartialJSONObject, Token } from '@lumino/coreutils';
|
|
|
3
3
|
import { ISignal } from '@lumino/signaling';
|
|
4
4
|
import { JSONSchema7 } from 'json-schema';
|
|
5
5
|
|
|
6
|
-
import { IBaseCompleter } from './
|
|
6
|
+
import { IBaseCompleter } from './base-completer';
|
|
7
7
|
|
|
8
8
|
export interface IDict<T = any> {
|
|
9
9
|
[key: string]: T;
|
package/lib/llm-models/index.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
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
|
-
import { AnthropicCompleter } from './anthropic-completer';
|
|
6
|
-
import { CodestralCompleter } from './codestral-completer';
|
|
7
|
-
import { ChromeCompleter } from './chrome-completer';
|
|
8
|
-
import { OpenAICompleter } from './openai-completer';
|
|
9
|
-
import { instructions } from '../settings/instructions';
|
|
10
|
-
import { ProviderSettings } from '../settings/schemas';
|
|
11
|
-
export * from './base-completer';
|
|
12
|
-
const AIProviders = [
|
|
13
|
-
{
|
|
14
|
-
name: 'Anthropic',
|
|
15
|
-
chatModel: ChatAnthropic,
|
|
16
|
-
completer: AnthropicCompleter,
|
|
17
|
-
settingsSchema: ProviderSettings.Anthropic,
|
|
18
|
-
errorMessage: (error) => error.error.error.message
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
name: 'ChromeAI',
|
|
22
|
-
// TODO: fix
|
|
23
|
-
// @ts-expect-error: missing properties
|
|
24
|
-
chatModel: ChromeAI,
|
|
25
|
-
completer: ChromeCompleter,
|
|
26
|
-
instructions: instructions.ChromeAI,
|
|
27
|
-
settingsSchema: ProviderSettings.ChromeAI
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
name: 'MistralAI',
|
|
31
|
-
chatModel: ChatMistralAI,
|
|
32
|
-
completer: CodestralCompleter,
|
|
33
|
-
instructions: instructions.MistralAI,
|
|
34
|
-
settingsSchema: ProviderSettings.MistralAI
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
name: 'OpenAI',
|
|
38
|
-
chatModel: ChatOpenAI,
|
|
39
|
-
completer: OpenAICompleter,
|
|
40
|
-
settingsSchema: ProviderSettings.OpenAI
|
|
41
|
-
}
|
|
42
|
-
];
|
|
43
|
-
export { AIProviders };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
const chromeAiInstructions = `
|
|
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
|
-
`;
|
|
25
|
-
const mistralAIInstructions = `
|
|
26
|
-
<i class="fas fa-exclamation-triangle"></i> This extension is still very much experimental. It is not an official MistralAI extension.
|
|
27
|
-
|
|
28
|
-
1. Go to https://console.mistral.ai/api-keys/ and create an API key.
|
|
29
|
-
|
|
30
|
-
<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">
|
|
31
|
-
|
|
32
|
-
2. Open the JupyterLab settings and go to the **Ai providers** section to select the \`MistralAI\`
|
|
33
|
-
provider and the API key (required).
|
|
34
|
-
|
|
35
|
-
<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">
|
|
36
|
-
|
|
37
|
-
3. Open the chat, or use the inline completer
|
|
38
|
-
|
|
39
|
-
<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">
|
|
40
|
-
`;
|
|
41
|
-
export const instructions = {
|
|
42
|
-
ChromeAI: chromeAiInstructions,
|
|
43
|
-
MistralAI: mistralAIInstructions
|
|
44
|
-
};
|