@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
@@ -0,0 +1,7 @@
1
+ {
2
+ "title": "AI provider",
3
+ "description": "Provider settings",
4
+ "type": "object",
5
+ "properties": {},
6
+ "additionalProperties": false
7
+ }
@@ -0,0 +1,3 @@
1
+ import { IDict } from '../../tokens';
2
+ declare const ProviderSettings: IDict<any>;
3
+ export { ProviderSettings };
@@ -0,0 +1,11 @@
1
+ import ChromeAI from './_generated/ChromeAI.json';
2
+ import MistralAI from './_generated/MistralAI.json';
3
+ import Anthropic from './_generated/Anthropic.json';
4
+ import OpenAI from './_generated/OpenAI.json';
5
+ const ProviderSettings = {
6
+ ChromeAI,
7
+ MistralAI,
8
+ Anthropic,
9
+ OpenAI
10
+ };
11
+ export { ProviderSettings };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * TODO: reuse from Jupyter AI instead of copying?
3
+ * https://github.com/jupyterlab/jupyter-ai/blob/main/packages/jupyter-ai/src/slash-autocompletion.tsx
4
+ */
5
+ /// <reference types="react-addons-linked-state-mixin" />
6
+ import { AutocompleteCommand } from '@jupyter/chat';
7
+ import React from 'react';
8
+ type SlashCommandOption = AutocompleteCommand & {
9
+ id: string;
10
+ description: string;
11
+ };
12
+ /**
13
+ * Renders an option shown in the slash command autocomplete.
14
+ */
15
+ export declare function renderSlashCommandOption(optionProps: React.HTMLAttributes<HTMLLIElement>, option: SlashCommandOption): JSX.Element;
16
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * TODO: reuse from Jupyter AI instead of copying?
3
+ * https://github.com/jupyterlab/jupyter-ai/blob/main/packages/jupyter-ai/src/slash-autocompletion.tsx
4
+ */
5
+ import { Box, Typography } from '@mui/material';
6
+ import HideSource from '@mui/icons-material/HideSource';
7
+ import React from 'react';
8
+ const DEFAULT_SLASH_COMMAND_ICONS = {
9
+ clear: React.createElement(HideSource, null)
10
+ };
11
+ /**
12
+ * Renders an option shown in the slash command autocomplete.
13
+ */
14
+ export function renderSlashCommandOption(optionProps, option) {
15
+ const icon = option.id in DEFAULT_SLASH_COMMAND_ICONS
16
+ ? DEFAULT_SLASH_COMMAND_ICONS[option.id]
17
+ : DEFAULT_SLASH_COMMAND_ICONS.unknown;
18
+ return (React.createElement("li", { ...optionProps },
19
+ React.createElement(Box, { sx: { lineHeight: 0, marginRight: 4, opacity: 0.618 } }, icon),
20
+ React.createElement(Box, { sx: { flexGrow: 1 } },
21
+ React.createElement(Typography, { component: "span", sx: {
22
+ fontSize: 'var(--jp-ui-font-size1)'
23
+ } }, option.label),
24
+ React.createElement(Typography, { component: "span", sx: { opacity: 0.618, fontSize: 'var(--jp-ui-font-size0)' } }, ' — ' + option.description))));
25
+ }
@@ -0,0 +1,103 @@
1
+ import { BaseChatModel } from '@langchain/core/language_models/chat_models';
2
+ import { ReadonlyPartialJSONObject, Token } from '@lumino/coreutils';
3
+ import { ISignal } from '@lumino/signaling';
4
+ import { JSONSchema7 } from 'json-schema';
5
+ import { IBaseCompleter } from './llm-models';
6
+ export interface IDict<T = any> {
7
+ [key: string]: T;
8
+ }
9
+ export interface IType<T> {
10
+ new (...args: any[]): T;
11
+ }
12
+ /**
13
+ * The provider interface.
14
+ */
15
+ export interface IAIProvider {
16
+ /**
17
+ * The name of the provider.
18
+ */
19
+ name: string;
20
+ /**
21
+ * The chat model class to use.
22
+ */
23
+ chatModel?: IType<BaseChatModel>;
24
+ /**
25
+ * The completer class to use.
26
+ */
27
+ completer?: IType<IBaseCompleter>;
28
+ /**
29
+ * the settings schema for the provider.
30
+ */
31
+ settingsSchema?: any;
32
+ /**
33
+ * The instructions to be displayed in the settings, as helper to use the provider.
34
+ * A markdown renderer is used to render the instructions.
35
+ */
36
+ instructions?: string;
37
+ /**
38
+ * A function that extract the error message from the provider API error.
39
+ * Default to `(error) => error.message`.
40
+ */
41
+ errorMessage?: (error: any) => string;
42
+ }
43
+ /**
44
+ * The provider registry interface.
45
+ */
46
+ export interface IAIProviderRegistry {
47
+ /**
48
+ * Get the list of provider names.
49
+ */
50
+ readonly providers: string[];
51
+ /**
52
+ * Add a new provider.
53
+ */
54
+ add(provider: IAIProvider): void;
55
+ /**
56
+ * Get the current provider name.
57
+ */
58
+ currentName: string;
59
+ /**
60
+ * Get the current completer of the completion provider.
61
+ */
62
+ currentCompleter: IBaseCompleter | null;
63
+ /**
64
+ * Get the current llm chat model.
65
+ */
66
+ currentChatModel: BaseChatModel | null;
67
+ /**
68
+ * Get the settings schema of a given provider.
69
+ */
70
+ getSettingsSchema(provider: string): JSONSchema7;
71
+ /**
72
+ * Get the instructions of a given provider.
73
+ */
74
+ getInstructions(provider: string): string | undefined;
75
+ /**
76
+ * Format an error message from the current provider.
77
+ */
78
+ formatErrorMessage(error: any): string;
79
+ /**
80
+ * Set the providers (chat model and completer).
81
+ * Creates the providers if the name has changed, otherwise only updates their config.
82
+ *
83
+ * @param name - the name of the provider to use.
84
+ * @param settings - the settings for the models.
85
+ */
86
+ setProvider(name: string, settings: ReadonlyPartialJSONObject): void;
87
+ /**
88
+ * A signal emitting when the provider or its settings has changed.
89
+ */
90
+ readonly providerChanged: ISignal<IAIProviderRegistry, void>;
91
+ /**
92
+ * Get the current chat error;
93
+ */
94
+ readonly chatError: string;
95
+ /**
96
+ * get the current completer error.
97
+ */
98
+ readonly completerError: string;
99
+ }
100
+ /**
101
+ * The provider registry token.
102
+ */
103
+ export declare const IAIProviderRegistry: Token<IAIProviderRegistry>;
package/lib/tokens.js ADDED
@@ -0,0 +1,5 @@
1
+ import { Token } from '@lumino/coreutils';
2
+ /**
3
+ * The provider registry token.
4
+ */
5
+ export const IAIProviderRegistry = new Token('@jupyterlite/ai:provider-registry', 'Provider for chat and completion LLM provider');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyterlite/ai",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "AI code completions and chat for JupyterLite",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -27,8 +27,9 @@
27
27
  "url": "https://github.com/jupyterlite/ai.git"
28
28
  },
29
29
  "scripts": {
30
- "build": "jlpm build:lib && jlpm build:labextension:dev",
31
- "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
30
+ "build": "node ./scripts/settings-generator.js && jlpm build:lib && jlpm build:labextension:dev",
31
+ "build:dev": "jlpm build:lib && jlpm build:labextension:dev",
32
+ "build:prod": "node ./scripts/settings-generator.js && jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
32
33
  "build:labextension": "jupyter labextension build .",
33
34
  "build:labextension:dev": "jupyter labextension build --development True .",
34
35
  "build:lib": "tsc --sourceMap",
@@ -53,21 +54,33 @@
53
54
  "watch:labextension": "jupyter labextension watch ."
54
55
  },
55
56
  "dependencies": {
56
- "@jupyter/chat": "^0.5.0",
57
- "@jupyterlab/application": "^4.2.0",
58
- "@jupyterlab/apputils": "^4.3.0",
59
- "@jupyterlab/completer": "^4.2.0",
60
- "@jupyterlab/notebook": "^4.2.0",
61
- "@jupyterlab/rendermime": "^4.2.0",
62
- "@jupyterlab/settingregistry": "^4.2.0",
63
- "@langchain/core": "^0.3.13",
57
+ "@jupyter/chat": "^0.7.1",
58
+ "@jupyterlab/application": "^4.4.0-alpha.0",
59
+ "@jupyterlab/apputils": "^4.5.0-alpha.0",
60
+ "@jupyterlab/completer": "^4.4.0-alpha.0",
61
+ "@jupyterlab/notebook": "^4.4.0-alpha.0",
62
+ "@jupyterlab/rendermime": "^4.4.0-alpha.0",
63
+ "@jupyterlab/settingregistry": "^4.4.0-alpha.0",
64
+ "@jupyterlab/ui-components": "^4.4.0-alpha.0",
65
+ "@langchain/anthropic": "^0.3.9",
66
+ "@langchain/community": "^0.3.31",
67
+ "@langchain/core": "^0.3.40",
64
68
  "@langchain/mistralai": "^0.1.1",
69
+ "@langchain/openai": "^0.4.4",
65
70
  "@lumino/coreutils": "^2.1.2",
66
71
  "@lumino/polling": "^2.1.2",
67
- "@lumino/signaling": "^2.1.2"
72
+ "@lumino/signaling": "^2.1.2",
73
+ "@mui/icons-material": "^5.11.0",
74
+ "@mui/material": "^5.11.0",
75
+ "@rjsf/core": "^4.2.0",
76
+ "@rjsf/utils": "^5.18.4",
77
+ "@rjsf/validator-ajv8": "^5.18.4",
78
+ "react": "^18.2.0",
79
+ "react-dom": "^18.2.0"
68
80
  },
69
81
  "devDependencies": {
70
82
  "@jupyterlab/builder": "^4.0.0",
83
+ "@stylistic/eslint-plugin": "^3.0.1",
71
84
  "@types/json-schema": "^7.0.11",
72
85
  "@types/react": "^18.0.26",
73
86
  "@types/react-addons-linked-state-mixin": "^0.14.22",
@@ -87,7 +100,8 @@
87
100
  "stylelint-config-standard": "^34.0.0",
88
101
  "stylelint-csstree-validator": "^3.0.0",
89
102
  "stylelint-prettier": "^4.0.0",
90
- "typescript": "~5.0.2",
103
+ "ts-json-schema-generator": "^2.3.0",
104
+ "typescript": "~5.1.6",
91
105
  "yjs": "^13.5.0"
92
106
  },
93
107
  "sideEffects": [
@@ -102,96 +116,5 @@
102
116
  "extension": true,
103
117
  "outputDir": "jupyterlite_ai/labextension",
104
118
  "schemaDir": "schema"
105
- },
106
- "eslintIgnore": [
107
- "node_modules",
108
- "dist",
109
- "coverage",
110
- "**/*.d.ts"
111
- ],
112
- "eslintConfig": {
113
- "extends": [
114
- "eslint:recommended",
115
- "plugin:@typescript-eslint/eslint-recommended",
116
- "plugin:@typescript-eslint/recommended",
117
- "plugin:prettier/recommended"
118
- ],
119
- "parser": "@typescript-eslint/parser",
120
- "parserOptions": {
121
- "project": "tsconfig.json",
122
- "sourceType": "module"
123
- },
124
- "plugins": [
125
- "@typescript-eslint"
126
- ],
127
- "rules": {
128
- "@typescript-eslint/naming-convention": [
129
- "error",
130
- {
131
- "selector": "interface",
132
- "format": [
133
- "PascalCase"
134
- ],
135
- "custom": {
136
- "regex": "^I[A-Z]",
137
- "match": true
138
- }
139
- }
140
- ],
141
- "@typescript-eslint/no-unused-vars": [
142
- "warn",
143
- {
144
- "args": "none"
145
- }
146
- ],
147
- "@typescript-eslint/no-explicit-any": "off",
148
- "@typescript-eslint/no-namespace": "off",
149
- "@typescript-eslint/no-use-before-define": "off",
150
- "@typescript-eslint/quotes": [
151
- "error",
152
- "single",
153
- {
154
- "avoidEscape": true,
155
- "allowTemplateLiterals": false
156
- }
157
- ],
158
- "curly": [
159
- "error",
160
- "all"
161
- ],
162
- "eqeqeq": "error",
163
- "prefer-arrow-callback": "error"
164
- }
165
- },
166
- "prettier": {
167
- "singleQuote": true,
168
- "trailingComma": "none",
169
- "arrowParens": "avoid",
170
- "endOfLine": "auto",
171
- "overrides": [
172
- {
173
- "files": "package.json",
174
- "options": {
175
- "tabWidth": 4
176
- }
177
- }
178
- ]
179
- },
180
- "stylelint": {
181
- "extends": [
182
- "stylelint-config-recommended",
183
- "stylelint-config-standard",
184
- "stylelint-prettier/recommended"
185
- ],
186
- "plugins": [
187
- "stylelint-csstree-validator"
188
- ],
189
- "rules": {
190
- "csstree/validator": true,
191
- "property-no-vendor-prefix": null,
192
- "selector-class-pattern": "^([a-z][A-z\\d]*)(-[A-z\\d]+)*$",
193
- "selector-no-vendor-prefix": null,
194
- "value-no-vendor-prefix": null
195
- }
196
119
  }
197
120
  }
package/schema/chat.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "title": "Chat configuration",
3
3
  "description": "Configuration for the chat panel",
4
+ "jupyter.lab.setting-icon": "jupyter-chat::chat",
5
+ "jupyter.lab.setting-icon-label": "Jupyter Chat",
4
6
  "type": "object",
5
7
  "properties": {
6
8
  "sendWithShiftEnter": {
@@ -14,6 +16,12 @@
14
16
  "type": "boolean",
15
17
  "default": true,
16
18
  "readOnly": false
19
+ },
20
+ "personaName": {
21
+ "type": "string",
22
+ "title": "AI persona name",
23
+ "description": "The name of the AI persona",
24
+ "default": "Jupyternaut"
17
25
  }
18
26
  },
19
27
  "additionalProperties": false
@@ -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
+ }
@@ -13,11 +13,20 @@ import type { BaseChatModel } from '@langchain/core/language_models/chat_models'
13
13
  import {
14
14
  AIMessage,
15
15
  HumanMessage,
16
- mergeMessageRuns
16
+ mergeMessageRuns,
17
+ SystemMessage
17
18
  } from '@langchain/core/messages';
18
19
  import { UUID } from '@lumino/coreutils';
19
- import { getErrorMessage } from './llm-models';
20
- import { IAIProvider } from './token';
20
+ import { chatSystemPrompt } from './provider';
21
+ import { IAIProviderRegistry } from './tokens';
22
+ import { jupyternautLiteIcon } from './icons';
23
+
24
+ /**
25
+ * The base64 encoded SVG string of the jupyternaut lite icon.
26
+ * Encode so it can be passed as avatar_url to jupyter-chat.
27
+ */
28
+ const AI_AVATAR_BASE64 = btoa(jupyternautLiteIcon.svgstr);
29
+ const AI_AVATAR = `data:image/svg+xml;base64,${AI_AVATAR_BASE64}`;
21
30
 
22
31
  export type ConnectionMessage = {
23
32
  type: 'connection';
@@ -27,28 +36,69 @@ export type ConnectionMessage = {
27
36
  export class ChatHandler extends ChatModel {
28
37
  constructor(options: ChatHandler.IOptions) {
29
38
  super(options);
30
- this._aiProvider = options.aiProvider;
31
- this._aiProvider.modelChange.connect(() => {
32
- this._errorMessage = this._aiProvider.chatError;
39
+ this._providerRegistry = options.providerRegistry;
40
+ this._prompt = chatSystemPrompt({
41
+ provider_name: this._providerRegistry.currentName
42
+ });
43
+
44
+ this._providerRegistry.providerChanged.connect(() => {
45
+ this._errorMessage = this._providerRegistry.chatError;
46
+ this._prompt = chatSystemPrompt({
47
+ provider_name: this._providerRegistry.currentName
48
+ });
33
49
  });
34
50
  }
35
51
 
36
52
  get provider(): BaseChatModel | null {
37
- return this._aiProvider.chatModel;
53
+ return this._providerRegistry.currentChatModel;
54
+ }
55
+
56
+ /**
57
+ * Getter and setter for the persona name.
58
+ */
59
+ get personaName(): string {
60
+ return this._personaName;
61
+ }
62
+ set personaName(value: string) {
63
+ this.messages.forEach(message => {
64
+ if (message.sender.username === this._personaName) {
65
+ const updated: IChatMessage = { ...message };
66
+ updated.sender.username = value;
67
+ this.messageAdded(updated);
68
+ }
69
+ });
70
+ this._personaName = value;
71
+ }
72
+
73
+ /**
74
+ * Getter and setter for the initial prompt.
75
+ */
76
+ get prompt(): string {
77
+ return this._prompt;
78
+ }
79
+ set prompt(value: string) {
80
+ this._prompt = value;
38
81
  }
39
82
 
40
83
  async sendMessage(message: INewMessage): Promise<boolean> {
84
+ const body = message.body;
85
+ if (body.startsWith('/clear')) {
86
+ // TODO: do we need a clear method?
87
+ this.messagesDeleted(0, this.messages.length);
88
+ this._history.messages = [];
89
+ return false;
90
+ }
41
91
  message.id = UUID.uuid4();
42
92
  const msg: IChatMessage = {
43
93
  id: message.id,
44
- body: message.body,
94
+ body,
45
95
  sender: { username: 'User' },
46
96
  time: Date.now(),
47
97
  type: 'msg'
48
98
  };
49
99
  this.messageAdded(msg);
50
100
 
51
- if (this._aiProvider.chatModel === null) {
101
+ if (this._providerRegistry.currentChatModel === null) {
52
102
  const errorMsg: IChatMessage = {
53
103
  id: UUID.uuid4(),
54
104
  body: `**${this._errorMessage ? this._errorMessage : this._defaultErrorMessage}**`,
@@ -62,8 +112,9 @@ export class ChatHandler extends ChatModel {
62
112
 
63
113
  this._history.messages.push(msg);
64
114
 
65
- const messages = mergeMessageRuns(
66
- this._history.messages.map(msg => {
115
+ const messages = mergeMessageRuns([new SystemMessage(this._prompt)]);
116
+ messages.push(
117
+ ...this._history.messages.map(msg => {
67
118
  if (msg.sender.username === 'User') {
68
119
  return new HumanMessage(msg.body);
69
120
  }
@@ -71,37 +122,44 @@ export class ChatHandler extends ChatModel {
71
122
  })
72
123
  );
73
124
 
74
- this.updateWriters([{ username: 'AI' }]);
75
- return this._aiProvider.chatModel
76
- .invoke(messages)
77
- .then(response => {
78
- const content = response.content;
79
- const botMsg: IChatMessage = {
80
- id: UUID.uuid4(),
81
- body: content.toString(),
82
- sender: { username: 'AI' },
83
- time: Date.now(),
84
- type: 'msg'
85
- };
125
+ const sender = { username: this._personaName, avatar_url: AI_AVATAR };
126
+ this.updateWriters([sender]);
127
+
128
+ // create an empty message to be filled by the AI provider
129
+ const botMsg: IChatMessage = {
130
+ id: UUID.uuid4(),
131
+ body: '',
132
+ sender,
133
+ time: Date.now(),
134
+ type: 'msg'
135
+ };
136
+
137
+ let content = '';
138
+
139
+ try {
140
+ for await (const chunk of await this._providerRegistry.currentChatModel.stream(
141
+ messages
142
+ )) {
143
+ content += chunk.content ?? chunk;
144
+ botMsg.body = content;
86
145
  this.messageAdded(botMsg);
87
- this._history.messages.push(botMsg);
88
- return true;
89
- })
90
- .catch(reason => {
91
- const error = getErrorMessage(this._aiProvider.name, reason);
92
- const errorMsg: IChatMessage = {
93
- id: UUID.uuid4(),
94
- body: `**${error}**`,
95
- sender: { username: 'ERROR' },
96
- time: Date.now(),
97
- type: 'msg'
98
- };
99
- this.messageAdded(errorMsg);
100
- return false;
101
- })
102
- .finally(() => {
103
- this.updateWriters([]);
104
- });
146
+ }
147
+ this._history.messages.push(botMsg);
148
+ return true;
149
+ } catch (reason) {
150
+ const error = this._providerRegistry.formatErrorMessage(reason);
151
+ const errorMsg: IChatMessage = {
152
+ id: UUID.uuid4(),
153
+ body: `**${error}**`,
154
+ sender: { username: 'ERROR' },
155
+ time: Date.now(),
156
+ type: 'msg'
157
+ };
158
+ this.messageAdded(errorMsg);
159
+ return false;
160
+ } finally {
161
+ this.updateWriters([]);
162
+ }
105
163
  }
106
164
 
107
165
  async getHistory(): Promise<IChatHistory> {
@@ -116,7 +174,9 @@ export class ChatHandler extends ChatModel {
116
174
  super.messageAdded(message);
117
175
  }
118
176
 
119
- private _aiProvider: IAIProvider;
177
+ private _providerRegistry: IAIProviderRegistry;
178
+ private _personaName = 'AI';
179
+ private _prompt: string;
120
180
  private _errorMessage: string = '';
121
181
  private _history: IChatHistory = { messages: [] };
122
182
  private _defaultErrorMessage = 'AI provider not configured';
@@ -124,6 +184,6 @@ export class ChatHandler extends ChatModel {
124
184
 
125
185
  export namespace ChatHandler {
126
186
  export interface IOptions extends ChatModel.IOptions {
127
- aiProvider: IAIProvider;
187
+ providerRegistry: IAIProviderRegistry;
128
188
  }
129
189
  }