@jupyterlite/ai 0.2.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 (66) hide show
  1. package/README.md +48 -9
  2. package/lib/chat-handler.d.ts +15 -3
  3. package/lib/chat-handler.js +80 -28
  4. package/lib/completion-provider.d.ts +5 -18
  5. package/lib/completion-provider.js +8 -34
  6. package/lib/icons.d.ts +2 -0
  7. package/lib/icons.js +15 -0
  8. package/lib/index.d.ts +3 -2
  9. package/lib/index.js +79 -22
  10. package/lib/llm-models/anthropic-completer.d.ts +19 -0
  11. package/lib/llm-models/anthropic-completer.js +57 -0
  12. package/lib/llm-models/base-completer.d.ts +6 -2
  13. package/lib/llm-models/chrome-completer.d.ts +19 -0
  14. package/lib/llm-models/chrome-completer.js +67 -0
  15. package/lib/llm-models/codestral-completer.d.ts +9 -8
  16. package/lib/llm-models/codestral-completer.js +37 -54
  17. package/lib/llm-models/index.d.ts +3 -2
  18. package/lib/llm-models/index.js +42 -2
  19. package/lib/llm-models/openai-completer.d.ts +19 -0
  20. package/lib/llm-models/openai-completer.js +51 -0
  21. package/lib/provider.d.ts +54 -15
  22. package/lib/provider.js +123 -41
  23. package/lib/settings/instructions.d.ts +2 -0
  24. package/lib/settings/instructions.js +44 -0
  25. package/lib/settings/panel.d.ts +70 -0
  26. package/lib/settings/panel.js +190 -0
  27. package/lib/settings/schemas/_generated/Anthropic.json +70 -0
  28. package/lib/settings/schemas/_generated/ChromeAI.json +21 -0
  29. package/lib/settings/schemas/_generated/MistralAI.json +75 -0
  30. package/lib/settings/schemas/_generated/OpenAI.json +668 -0
  31. package/lib/settings/schemas/base.json +7 -0
  32. package/lib/settings/schemas/index.d.ts +3 -0
  33. package/lib/settings/schemas/index.js +11 -0
  34. package/lib/slash-commands.d.ts +16 -0
  35. package/lib/slash-commands.js +25 -0
  36. package/lib/tokens.d.ts +103 -0
  37. package/lib/tokens.js +5 -0
  38. package/package.json +27 -104
  39. package/schema/chat.json +8 -0
  40. package/schema/provider-registry.json +17 -0
  41. package/src/chat-handler.ts +103 -43
  42. package/src/completion-provider.ts +13 -37
  43. package/src/icons.ts +18 -0
  44. package/src/index.ts +101 -24
  45. package/src/llm-models/anthropic-completer.ts +75 -0
  46. package/src/llm-models/base-completer.ts +7 -2
  47. package/src/llm-models/chrome-completer.ts +88 -0
  48. package/src/llm-models/codestral-completer.ts +43 -69
  49. package/src/llm-models/index.ts +49 -2
  50. package/src/llm-models/openai-completer.ts +67 -0
  51. package/src/llm-models/svg.d.ts +9 -0
  52. package/src/provider.ts +138 -43
  53. package/src/settings/instructions.ts +48 -0
  54. package/src/settings/panel.tsx +257 -0
  55. package/src/settings/schemas/index.ts +15 -0
  56. package/src/slash-commands.tsx +55 -0
  57. package/src/tokens.ts +112 -0
  58. package/style/base.css +4 -0
  59. package/style/icons/jupyternaut-lite.svg +7 -0
  60. package/lib/llm-models/utils.d.ts +0 -15
  61. package/lib/llm-models/utils.js +0 -29
  62. package/lib/token.d.ts +0 -13
  63. package/lib/token.js +0 -2
  64. package/schema/ai-provider.json +0 -21
  65. package/src/llm-models/utils.ts +0 -41
  66. package/src/token.ts +0 -19
package/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  [![Github Actions Status](https://github.com/jupyterlite/ai/workflows/Build/badge.svg)](https://github.com/jupyterlite/ai/actions/workflows/build.yml)
4
4
  [![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://jupyterlite.github.io/ai/lab/index.html)
5
5
 
6
- AI code completions and chat for JupyterLab, Notebook 7 and JupyterLite, powered by MistralAI
6
+ AI code completions and chat for JupyterLab, Notebook 7 and JupyterLite ✨
7
7
 
8
- [a screencast showing the Codestral extension in JupyterLite](https://github.com/jupyterlite/ai/assets/591645/855c4e3e-3a63-4868-8052-5c9909922c21)
8
+ [a screencast showing the Jupyterlite AI extension in JupyterLite](https://github.com/jupyterlite/ai/assets/591645/855c4e3e-3a63-4868-8052-5c9909922c21)
9
9
 
10
10
  ## Requirements
11
11
 
@@ -14,12 +14,7 @@ AI code completions and chat for JupyterLab, Notebook 7 and JupyterLite, powered
14
14
  > To enable more AI providers in JupyterLab and Jupyter Notebook, we recommend using the [Jupyter AI](https://github.com/jupyterlab/jupyter-ai) extension directly.
15
15
  > At the moment Jupyter AI is not compatible with JupyterLite, but might be to some extent in the future.
16
16
 
17
- - JupyterLab >= 4.1.0 or Notebook >= 7.1.0
18
-
19
- > [!WARNING]
20
- > This extension is still very much experimental. It is not an official MistralAI extension.
21
- > It is exploring the integration of the MistralAI API with JupyterLab, which can also be used in [JupyterLite](https://jupyterlite.readthedocs.io/).
22
- > For a more complete AI extension for JupyterLab, see [Jupyter AI](https://github.com/jupyterlab/jupyter-ai).
17
+ - JupyterLab >= 4.4.0a0 or Notebook >= 7.4.0a0
23
18
 
24
19
  ## ✨ Try it in your browser ✨
25
20
 
@@ -37,13 +32,29 @@ To install the extension, execute:
37
32
  pip install jupyterlite-ai
38
33
  ```
39
34
 
35
+ To install requirements (jupyterlab, jupyterlite and notebook), there is an optional dependencies argument:
36
+
37
+ ```bash
38
+ pip install jupyterlite-ai[jupyter]
39
+ ```
40
+
40
41
  # Usage
41
42
 
43
+ AI providers typically require using an API key to access their models.
44
+
45
+ The process is different for each provider, so you may refer to their documentation to learn how to generate new API keys, if they are not covered in the sections below.
46
+
47
+ ## Using MistralAI
48
+
49
+ > [!WARNING]
50
+ > This extension is still very much experimental. It is not an official MistralAI extension.
51
+
42
52
  1. Go to https://console.mistral.ai/api-keys/ and create an API key.
43
53
 
44
54
  ![Screenshot showing how to create an API key](./img/1-api-key.png)
45
55
 
46
- 2. Open the JupyterLab settings and go to the Codestral section to enter the API key
56
+ 2. Open the JupyterLab settings and go to the **Ai providers** section to select the `MistralAI`
57
+ provider and the API key (required).
47
58
 
48
59
  ![Screenshot showing how to add the API key to the settings](./img/2-jupyterlab-settings.png)
49
60
 
@@ -51,6 +62,34 @@ pip install jupyterlite-ai
51
62
 
52
63
  ![Screenshot showing how to use the chat](./img/3-usage.png)
53
64
 
65
+ ## Using ChromeAI
66
+
67
+ > [!WARNING]
68
+ > Support for ChromeAI is still experimental and only available in Google Chrome.
69
+
70
+ You can test ChromeAI is enabled in your browser by going to the following URL: https://chromeai.org/
71
+
72
+ Enable the proper flags in Google Chrome.
73
+
74
+ - chrome://flags/#prompt-api-for-gemini-nano
75
+ - Select: `Enabled`
76
+ - chrome://flags/#optimization-guide-on-device-model
77
+ - Select: `Enabled BypassPrefRequirement`
78
+ - chrome://components
79
+ - Click `Check for Update` on Optimization Guide On Device Model to download the model
80
+ - [Optional] chrome://flags/#text-safety-classifier
81
+
82
+ ![a screenshot showing how to enable the ChromeAI flag in Google Chrome](https://github.com/user-attachments/assets/d48f46cc-52ee-4ce5-9eaf-c763cdbee04c)
83
+
84
+ Then restart Chrome for these changes to take effect.
85
+
86
+ > [!WARNING]
87
+ > 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).
88
+ > During the download, ChromeAI may not be available via the extension.
89
+
90
+ > [!NOTE]
91
+ > For more information about Chrome Built-in AI: https://developer.chrome.com/docs/ai/get-started
92
+
54
93
  ## Uninstall
55
94
 
56
95
  To remove the extension, execute:
@@ -1,6 +1,6 @@
1
1
  import { ChatModel, IChatHistory, IChatMessage, INewMessage } from '@jupyter/chat';
2
2
  import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
3
- import { IAIProvider } from './token';
3
+ import { IAIProviderRegistry } from './tokens';
4
4
  export type ConnectionMessage = {
5
5
  type: 'connection';
6
6
  client_id: string;
@@ -8,17 +8,29 @@ export type ConnectionMessage = {
8
8
  export declare class ChatHandler extends ChatModel {
9
9
  constructor(options: ChatHandler.IOptions);
10
10
  get provider(): BaseChatModel | null;
11
+ /**
12
+ * Getter and setter for the persona name.
13
+ */
14
+ get personaName(): string;
15
+ set personaName(value: string);
16
+ /**
17
+ * Getter and setter for the initial prompt.
18
+ */
19
+ get prompt(): string;
20
+ set prompt(value: string);
11
21
  sendMessage(message: INewMessage): Promise<boolean>;
12
22
  getHistory(): Promise<IChatHistory>;
13
23
  dispose(): void;
14
24
  messageAdded(message: IChatMessage): void;
15
- private _aiProvider;
25
+ private _providerRegistry;
26
+ private _personaName;
27
+ private _prompt;
16
28
  private _errorMessage;
17
29
  private _history;
18
30
  private _defaultErrorMessage;
19
31
  }
20
32
  export declare namespace ChatHandler {
21
33
  interface IOptions extends ChatModel.IOptions {
22
- aiProvider: IAIProvider;
34
+ providerRegistry: IAIProviderRegistry;
23
35
  }
24
36
  }
@@ -3,34 +3,81 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import { ChatModel } from '@jupyter/chat';
6
- import { AIMessage, HumanMessage, mergeMessageRuns } from '@langchain/core/messages';
6
+ import { AIMessage, HumanMessage, mergeMessageRuns, SystemMessage } from '@langchain/core/messages';
7
7
  import { UUID } from '@lumino/coreutils';
8
- import { getErrorMessage } from './llm-models';
8
+ import { chatSystemPrompt } from './provider';
9
+ import { jupyternautLiteIcon } from './icons';
10
+ /**
11
+ * The base64 encoded SVG string of the jupyternaut lite icon.
12
+ * Encode so it can be passed as avatar_url to jupyter-chat.
13
+ */
14
+ const AI_AVATAR_BASE64 = btoa(jupyternautLiteIcon.svgstr);
15
+ const AI_AVATAR = `data:image/svg+xml;base64,${AI_AVATAR_BASE64}`;
9
16
  export class ChatHandler extends ChatModel {
10
17
  constructor(options) {
11
18
  super(options);
19
+ this._personaName = 'AI';
12
20
  this._errorMessage = '';
13
21
  this._history = { messages: [] };
14
22
  this._defaultErrorMessage = 'AI provider not configured';
15
- this._aiProvider = options.aiProvider;
16
- this._aiProvider.modelChange.connect(() => {
17
- this._errorMessage = this._aiProvider.chatError;
23
+ this._providerRegistry = options.providerRegistry;
24
+ this._prompt = chatSystemPrompt({
25
+ provider_name: this._providerRegistry.currentName
26
+ });
27
+ this._providerRegistry.providerChanged.connect(() => {
28
+ this._errorMessage = this._providerRegistry.chatError;
29
+ this._prompt = chatSystemPrompt({
30
+ provider_name: this._providerRegistry.currentName
31
+ });
18
32
  });
19
33
  }
20
34
  get provider() {
21
- return this._aiProvider.chatModel;
35
+ return this._providerRegistry.currentChatModel;
36
+ }
37
+ /**
38
+ * Getter and setter for the persona name.
39
+ */
40
+ get personaName() {
41
+ return this._personaName;
42
+ }
43
+ set personaName(value) {
44
+ this.messages.forEach(message => {
45
+ if (message.sender.username === this._personaName) {
46
+ const updated = { ...message };
47
+ updated.sender.username = value;
48
+ this.messageAdded(updated);
49
+ }
50
+ });
51
+ this._personaName = value;
52
+ }
53
+ /**
54
+ * Getter and setter for the initial prompt.
55
+ */
56
+ get prompt() {
57
+ return this._prompt;
58
+ }
59
+ set prompt(value) {
60
+ this._prompt = value;
22
61
  }
23
62
  async sendMessage(message) {
63
+ var _a;
64
+ const body = message.body;
65
+ if (body.startsWith('/clear')) {
66
+ // TODO: do we need a clear method?
67
+ this.messagesDeleted(0, this.messages.length);
68
+ this._history.messages = [];
69
+ return false;
70
+ }
24
71
  message.id = UUID.uuid4();
25
72
  const msg = {
26
73
  id: message.id,
27
- body: message.body,
74
+ body,
28
75
  sender: { username: 'User' },
29
76
  time: Date.now(),
30
77
  type: 'msg'
31
78
  };
32
79
  this.messageAdded(msg);
33
- if (this._aiProvider.chatModel === null) {
80
+ if (this._providerRegistry.currentChatModel === null) {
34
81
  const errorMsg = {
35
82
  id: UUID.uuid4(),
36
83
  body: `**${this._errorMessage ? this._errorMessage : this._defaultErrorMessage}**`,
@@ -42,30 +89,35 @@ export class ChatHandler extends ChatModel {
42
89
  return false;
43
90
  }
44
91
  this._history.messages.push(msg);
45
- const messages = mergeMessageRuns(this._history.messages.map(msg => {
92
+ const messages = mergeMessageRuns([new SystemMessage(this._prompt)]);
93
+ messages.push(...this._history.messages.map(msg => {
46
94
  if (msg.sender.username === 'User') {
47
95
  return new HumanMessage(msg.body);
48
96
  }
49
97
  return new AIMessage(msg.body);
50
98
  }));
51
- this.updateWriters([{ username: 'AI' }]);
52
- return this._aiProvider.chatModel
53
- .invoke(messages)
54
- .then(response => {
55
- const content = response.content;
56
- const botMsg = {
57
- id: UUID.uuid4(),
58
- body: content.toString(),
59
- sender: { username: 'AI' },
60
- time: Date.now(),
61
- type: 'msg'
62
- };
63
- this.messageAdded(botMsg);
99
+ const sender = { username: this._personaName, avatar_url: AI_AVATAR };
100
+ this.updateWriters([sender]);
101
+ // create an empty message to be filled by the AI provider
102
+ const botMsg = {
103
+ id: UUID.uuid4(),
104
+ body: '',
105
+ sender,
106
+ time: Date.now(),
107
+ type: 'msg'
108
+ };
109
+ let content = '';
110
+ try {
111
+ for await (const chunk of await this._providerRegistry.currentChatModel.stream(messages)) {
112
+ content += (_a = chunk.content) !== null && _a !== void 0 ? _a : chunk;
113
+ botMsg.body = content;
114
+ this.messageAdded(botMsg);
115
+ }
64
116
  this._history.messages.push(botMsg);
65
117
  return true;
66
- })
67
- .catch(reason => {
68
- const error = getErrorMessage(this._aiProvider.name, reason);
118
+ }
119
+ catch (reason) {
120
+ const error = this._providerRegistry.formatErrorMessage(reason);
69
121
  const errorMsg = {
70
122
  id: UUID.uuid4(),
71
123
  body: `**${error}**`,
@@ -75,10 +127,10 @@ export class ChatHandler extends ChatModel {
75
127
  };
76
128
  this.messageAdded(errorMsg);
77
129
  return false;
78
- })
79
- .finally(() => {
130
+ }
131
+ finally {
80
132
  this.updateWriters([]);
81
- });
133
+ }
82
134
  }
83
135
  async getHistory() {
84
136
  return this._history;
@@ -1,20 +1,12 @@
1
1
  import { CompletionHandler, IInlineCompletionContext, IInlineCompletionProvider } from '@jupyterlab/completer';
2
- import { LLM } from '@langchain/core/language_models/llms';
3
- import { IBaseCompleter, BaseCompleter } from './llm-models';
4
- import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
2
+ import { IBaseCompleter } from './llm-models';
3
+ import { IAIProviderRegistry } from './tokens';
5
4
  /**
6
5
  * The generic completion provider to register to the completion provider manager.
7
6
  */
8
7
  export declare class CompletionProvider implements IInlineCompletionProvider {
9
8
  readonly identifier = "@jupyterlite/ai";
10
9
  constructor(options: CompletionProvider.IOptions);
11
- /**
12
- * Set the completer.
13
- *
14
- * @param name - the name of the completer.
15
- * @param settings - The settings associated to the completer.
16
- */
17
- setCompleter(name: string, settings: ReadonlyPartialJSONObject): void;
18
10
  /**
19
11
  * Get the current completer name.
20
12
  */
@@ -23,18 +15,13 @@ export declare class CompletionProvider implements IInlineCompletionProvider {
23
15
  * Get the current completer.
24
16
  */
25
17
  get completer(): IBaseCompleter | null;
26
- /**
27
- * Get the LLM completer.
28
- */
29
- get llmCompleter(): LLM | null;
30
18
  fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<any>;
31
- private _name;
19
+ private _providerRegistry;
32
20
  private _requestCompletion;
33
- private _completer;
34
21
  }
35
22
  export declare namespace CompletionProvider {
36
- interface IOptions extends BaseCompleter.IOptions {
37
- name: string;
23
+ interface IOptions {
24
+ providerRegistry: IAIProviderRegistry;
38
25
  requestCompletion: () => void;
39
26
  }
40
27
  }
@@ -1,57 +1,31 @@
1
- import { getCompleter } from './llm-models';
2
1
  /**
3
2
  * The generic completion provider to register to the completion provider manager.
4
3
  */
5
4
  export class CompletionProvider {
6
5
  constructor(options) {
7
6
  this.identifier = '@jupyterlite/ai';
8
- this._name = 'None';
9
- this._completer = null;
10
- const { name, settings } = options;
7
+ this._providerRegistry = options.providerRegistry;
11
8
  this._requestCompletion = options.requestCompletion;
12
- this.setCompleter(name, settings);
13
- }
14
- /**
15
- * Set the completer.
16
- *
17
- * @param name - the name of the completer.
18
- * @param settings - The settings associated to the completer.
19
- */
20
- setCompleter(name, settings) {
21
- try {
22
- this._completer = getCompleter(name, settings);
23
- if (this._completer) {
24
- this._completer.requestCompletion = this._requestCompletion;
9
+ this._providerRegistry.providerChanged.connect(() => {
10
+ if (this.completer) {
11
+ this.completer.requestCompletion = this._requestCompletion;
25
12
  }
26
- this._name = this._completer === null ? 'None' : name;
27
- }
28
- catch (e) {
29
- this._completer = null;
30
- this._name = 'None';
31
- throw e;
32
- }
13
+ });
33
14
  }
34
15
  /**
35
16
  * Get the current completer name.
36
17
  */
37
18
  get name() {
38
- return this._name;
19
+ return this._providerRegistry.currentName;
39
20
  }
40
21
  /**
41
22
  * Get the current completer.
42
23
  */
43
24
  get completer() {
44
- return this._completer;
45
- }
46
- /**
47
- * Get the LLM completer.
48
- */
49
- get llmCompleter() {
50
- var _a;
51
- return ((_a = this._completer) === null || _a === void 0 ? void 0 : _a.provider) || null;
25
+ return this._providerRegistry.currentCompleter;
52
26
  }
53
27
  async fetch(request, context) {
54
28
  var _a;
55
- return (_a = this._completer) === null || _a === void 0 ? void 0 : _a.fetch(request, context);
29
+ return (_a = this.completer) === null || _a === void 0 ? void 0 : _a.fetch(request, context);
56
30
  }
57
31
  }
package/lib/icons.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { LabIcon } from '@jupyterlab/ui-components';
2
+ export declare const jupyternautLiteIcon: LabIcon;
package/lib/icons.js ADDED
@@ -0,0 +1,15 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { LabIcon } from '@jupyterlab/ui-components';
6
+ /**
7
+ * This icon is based on the jupyternaut icon from Jupyter AI:
8
+ * https://github.com/jupyterlab/jupyter-ai/blob/main/packages/jupyter-ai/style/icons/jupyternaut.svg
9
+ * With a small tweak for the colors to match the JupyterLite icon.
10
+ */
11
+ import jupyternautLiteSvg from '../style/icons/jupyternaut-lite.svg';
12
+ export const jupyternautLiteIcon = new LabIcon({
13
+ name: '@jupyterlite/ai:jupyternaut-lite',
14
+ svgstr: jupyternautLiteSvg
15
+ });
package/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
+ import { IAutocompletionRegistry } from '@jupyter/chat';
1
2
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
- import { IAIProvider } from './token';
3
- declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IAIProvider>)[];
3
+ import { IAIProviderRegistry } from './tokens';
4
+ declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IAutocompletionRegistry> | JupyterFrontEndPlugin<IAIProviderRegistry>)[];
4
5
  export default _default;
package/lib/index.js CHANGED
@@ -1,19 +1,49 @@
1
- import { ActiveCellManager, buildChatSidebar, buildErrorWidget } from '@jupyter/chat';
1
+ import { ActiveCellManager, AutocompletionRegistry, buildChatSidebar, buildErrorWidget, IAutocompletionRegistry } from '@jupyter/chat';
2
2
  import { IThemeManager } from '@jupyterlab/apputils';
3
3
  import { ICompletionProviderManager } from '@jupyterlab/completer';
4
4
  import { INotebookTracker } from '@jupyterlab/notebook';
5
5
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
6
6
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
7
+ import { IFormRendererRegistry } from '@jupyterlab/ui-components';
7
8
  import { ChatHandler } from './chat-handler';
8
- import { AIProvider } from './provider';
9
- import { IAIProvider } from './token';
9
+ import { CompletionProvider } from './completion-provider';
10
+ import { AIProviders } from './llm-models';
11
+ import { AIProviderRegistry } from './provider';
12
+ import { aiSettingsRenderer } from './settings/panel';
13
+ import { renderSlashCommandOption } from './slash-commands';
14
+ import { IAIProviderRegistry } from './tokens';
15
+ const autocompletionRegistryPlugin = {
16
+ id: '@jupyterlite/ai:autocompletion-registry',
17
+ description: 'Autocompletion registry',
18
+ autoStart: true,
19
+ provides: IAutocompletionRegistry,
20
+ activate: () => {
21
+ const autocompletionRegistry = new AutocompletionRegistry();
22
+ const options = ['/clear'];
23
+ const autocompletionCommands = {
24
+ opener: '/',
25
+ commands: options.map(option => {
26
+ return {
27
+ id: option.slice(1),
28
+ label: option,
29
+ description: 'Clear the chat window'
30
+ };
31
+ }),
32
+ props: {
33
+ renderOption: renderSlashCommandOption
34
+ }
35
+ };
36
+ autocompletionRegistry.add('jupyterlite-ai', autocompletionCommands);
37
+ return autocompletionRegistry;
38
+ }
39
+ };
10
40
  const chatPlugin = {
11
41
  id: '@jupyterlite/ai:chat',
12
42
  description: 'LLM chat extension',
13
43
  autoStart: true,
44
+ requires: [IAIProviderRegistry, IRenderMimeRegistry, IAutocompletionRegistry],
14
45
  optional: [INotebookTracker, ISettingRegistry, IThemeManager],
15
- requires: [IAIProvider, IRenderMimeRegistry],
16
- activate: async (app, aiProvider, rmRegistry, notebookTracker, settingsRegistry, themeManager) => {
46
+ activate: async (app, providerRegistry, rmRegistry, autocompletionRegistry, notebookTracker, settingsRegistry, themeManager) => {
17
47
  let activeCellManager = null;
18
48
  if (notebookTracker) {
19
49
  activeCellManager = new ActiveCellManager({
@@ -22,16 +52,20 @@ const chatPlugin = {
22
52
  });
23
53
  }
24
54
  const chatHandler = new ChatHandler({
25
- aiProvider: aiProvider,
26
- activeCellManager: activeCellManager
55
+ providerRegistry,
56
+ activeCellManager
27
57
  });
28
58
  let sendWithShiftEnter = false;
29
59
  let enableCodeToolbar = true;
60
+ let personaName = 'AI';
30
61
  function loadSetting(setting) {
31
62
  sendWithShiftEnter = setting.get('sendWithShiftEnter')
32
63
  .composite;
33
64
  enableCodeToolbar = setting.get('enableCodeToolbar').composite;
65
+ personaName = setting.get('personaName').composite;
66
+ // set the properties
34
67
  chatHandler.config = { sendWithShiftEnter, enableCodeToolbar };
68
+ chatHandler.personaName = personaName;
35
69
  }
36
70
  Promise.all([app.restored, settingsRegistry === null || settingsRegistry === void 0 ? void 0 : settingsRegistry.load(chatPlugin.id)])
37
71
  .then(([, settings]) => {
@@ -50,9 +84,10 @@ const chatPlugin = {
50
84
  chatWidget = buildChatSidebar({
51
85
  model: chatHandler,
52
86
  themeManager,
53
- rmRegistry
87
+ rmRegistry,
88
+ autocompletionRegistry
54
89
  });
55
- chatWidget.title.caption = 'Codestral Chat';
90
+ chatWidget.title.caption = 'Jupyterlite AI Chat';
56
91
  }
57
92
  catch (e) {
58
93
  chatWidget = buildErrorWidget(themeManager);
@@ -61,30 +96,52 @@ const chatPlugin = {
61
96
  console.log('Chat extension initialized');
62
97
  }
63
98
  };
64
- const aiProviderPlugin = {
65
- id: '@jupyterlite/ai:ai-provider',
99
+ const completerPlugin = {
100
+ id: '@jupyterlite/ai:completer',
66
101
  autoStart: true,
67
- requires: [ICompletionProviderManager, ISettingRegistry],
68
- provides: IAIProvider,
69
- activate: (app, manager, settingRegistry) => {
70
- const aiProvider = new AIProvider({
71
- completionProviderManager: manager,
102
+ requires: [IAIProviderRegistry, ICompletionProviderManager],
103
+ activate: (app, providerRegistry, manager) => {
104
+ const completer = new CompletionProvider({
105
+ providerRegistry,
72
106
  requestCompletion: () => app.commands.execute('inline-completer:invoke')
73
107
  });
108
+ manager.registerInlineProvider(completer);
109
+ }
110
+ };
111
+ const providerRegistryPlugin = {
112
+ id: '@jupyterlite/ai:provider-registry',
113
+ autoStart: true,
114
+ requires: [IFormRendererRegistry, ISettingRegistry],
115
+ optional: [IRenderMimeRegistry],
116
+ provides: IAIProviderRegistry,
117
+ activate: (app, editorRegistry, settingRegistry, rmRegistry) => {
118
+ const providerRegistry = new AIProviderRegistry();
119
+ editorRegistry.addRenderer('@jupyterlite/ai:provider-registry.AIprovider', aiSettingsRenderer({ providerRegistry, rmRegistry }));
74
120
  settingRegistry
75
- .load(aiProviderPlugin.id)
121
+ .load(providerRegistryPlugin.id)
76
122
  .then(settings => {
77
123
  const updateProvider = () => {
78
- const provider = settings.get('provider').composite;
79
- aiProvider.setModels(provider, settings.composite);
124
+ var _a;
125
+ // Update the settings to the AI providers.
126
+ const providerSettings = ((_a = settings.get('AIprovider').composite) !== null && _a !== void 0 ? _a : {
127
+ provider: 'None'
128
+ });
129
+ providerRegistry.setProvider(providerSettings.provider, providerSettings);
80
130
  };
81
131
  settings.changed.connect(() => updateProvider());
82
132
  updateProvider();
83
133
  })
84
134
  .catch(reason => {
85
- console.error(`Failed to load settings for ${aiProviderPlugin.id}`, reason);
135
+ console.error(`Failed to load settings for ${providerRegistryPlugin.id}`, reason);
86
136
  });
87
- return aiProvider;
137
+ // Initialize the registry with the default providers
138
+ AIProviders.forEach(provider => providerRegistry.add(provider));
139
+ return providerRegistry;
88
140
  }
89
141
  };
90
- export default [chatPlugin, aiProviderPlugin];
142
+ export default [
143
+ providerRegistryPlugin,
144
+ autocompletionRegistryPlugin,
145
+ chatPlugin,
146
+ completerPlugin
147
+ ];
@@ -0,0 +1,19 @@
1
+ import { CompletionHandler, IInlineCompletionContext } from '@jupyterlab/completer';
2
+ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
3
+ import { BaseCompleter, IBaseCompleter } from './base-completer';
4
+ export declare class AnthropicCompleter implements IBaseCompleter {
5
+ constructor(options: BaseCompleter.IOptions);
6
+ get provider(): BaseChatModel;
7
+ /**
8
+ * Getter and setter for the initial prompt.
9
+ */
10
+ get prompt(): string;
11
+ set prompt(value: string);
12
+ fetch(request: CompletionHandler.IRequest, context: IInlineCompletionContext): Promise<{
13
+ items: {
14
+ insertText: string;
15
+ }[];
16
+ }>;
17
+ private _anthropicProvider;
18
+ private _prompt;
19
+ }
@@ -0,0 +1,57 @@
1
+ import { ChatAnthropic } from '@langchain/anthropic';
2
+ import { AIMessage, SystemMessage } from '@langchain/core/messages';
3
+ import { COMPLETION_SYSTEM_PROMPT } from '../provider';
4
+ export class AnthropicCompleter {
5
+ constructor(options) {
6
+ this._prompt = COMPLETION_SYSTEM_PROMPT;
7
+ this._anthropicProvider = new ChatAnthropic({ ...options.settings });
8
+ }
9
+ get provider() {
10
+ return this._anthropicProvider;
11
+ }
12
+ /**
13
+ * Getter and setter for the initial prompt.
14
+ */
15
+ get prompt() {
16
+ return this._prompt;
17
+ }
18
+ set prompt(value) {
19
+ this._prompt = value;
20
+ }
21
+ async fetch(request, context) {
22
+ const { text, offset: cursorOffset } = request;
23
+ const prompt = text.slice(0, cursorOffset);
24
+ // Anthropic does not allow whitespace at the end of the AIMessage
25
+ const trimmedPrompt = prompt.trim();
26
+ const messages = [
27
+ new SystemMessage(this._prompt),
28
+ new AIMessage(trimmedPrompt)
29
+ ];
30
+ try {
31
+ const response = await this._anthropicProvider.invoke(messages);
32
+ const items = [];
33
+ // Anthropic can return string or complex content, a list of string/images/other.
34
+ if (typeof response.content === 'string') {
35
+ items.push({
36
+ insertText: response.content
37
+ });
38
+ }
39
+ else {
40
+ response.content.forEach(content => {
41
+ if (content.type !== 'text') {
42
+ return;
43
+ }
44
+ items.push({
45
+ insertText: content.text,
46
+ filterText: prompt.substring(trimmedPrompt.length)
47
+ });
48
+ });
49
+ }
50
+ return { items };
51
+ }
52
+ catch (error) {
53
+ console.error('Error fetching completions', error);
54
+ return { items: [] };
55
+ }
56
+ }
57
+ }