@jupyterlite/ai 0.12.0 → 0.14.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/agent.d.ts +36 -2
- package/lib/agent.js +249 -24
- package/lib/{chat-model-registry.d.ts → chat-model-handler.d.ts} +12 -11
- package/lib/{chat-model-registry.js → chat-model-handler.js} +6 -40
- package/lib/chat-model.d.ts +8 -0
- package/lib/chat-model.js +156 -8
- package/lib/completion/completion-provider.d.ts +1 -1
- package/lib/completion/completion-provider.js +14 -2
- package/lib/components/model-select.js +4 -4
- package/lib/components/tool-select.d.ts +11 -2
- package/lib/components/tool-select.js +77 -18
- package/lib/index.d.ts +3 -3
- package/lib/index.js +249 -117
- package/lib/models/settings-model.d.ts +2 -0
- package/lib/models/settings-model.js +2 -0
- package/lib/providers/built-in-providers.js +33 -32
- package/lib/providers/provider-tools.d.ts +36 -0
- package/lib/providers/provider-tools.js +93 -0
- package/lib/rendered-message-outputarea.d.ts +24 -0
- package/lib/rendered-message-outputarea.js +48 -0
- package/lib/tokens.d.ts +65 -7
- package/lib/tokens.js +1 -1
- package/lib/tools/commands.js +62 -22
- package/lib/tools/web.d.ts +8 -0
- package/lib/tools/web.js +196 -0
- package/lib/widgets/ai-settings.d.ts +4 -9
- package/lib/widgets/ai-settings.js +123 -69
- package/lib/widgets/main-area-chat.d.ts +6 -0
- package/lib/widgets/main-area-chat.js +28 -0
- package/lib/widgets/provider-config-dialog.js +211 -11
- package/package.json +17 -11
- package/schema/settings-model.json +89 -1
- package/src/agent.ts +327 -42
- package/src/{chat-model-registry.ts → chat-model-handler.ts} +16 -51
- package/src/chat-model.ts +223 -14
- package/src/completion/completion-provider.ts +26 -12
- package/src/components/model-select.tsx +4 -5
- package/src/components/tool-select.tsx +110 -7
- package/src/index.ts +359 -184
- package/src/models/settings-model.ts +6 -0
- package/src/providers/built-in-providers.ts +33 -32
- package/src/providers/provider-tools.ts +179 -0
- package/src/rendered-message-outputarea.ts +62 -0
- package/src/tokens.ts +82 -9
- package/src/tools/commands.ts +99 -31
- package/src/tools/web.ts +238 -0
- package/src/widgets/ai-settings.tsx +279 -124
- package/src/widgets/main-area-chat.ts +34 -1
- package/src/widgets/provider-config-dialog.tsx +504 -11
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ILabShell, ILayoutRestorer } from '@jupyterlab/application';
|
|
2
|
-
import { ActiveCellManager, AttachmentOpenerRegistry, chatIcon, ChatCommandRegistry, ChatWidget, IChatCommandRegistry, IInputToolbarRegistryFactory, InputToolbarRegistry, MultiChatPanel } from '@jupyter/chat';
|
|
3
|
-
import { ICommandPalette, IThemeManager, WidgetTracker } from '@jupyterlab/apputils';
|
|
2
|
+
import { ActiveCellManager, AttachmentOpenerRegistry, chatIcon, ChatCommandRegistry, ChatWidget, IChatCommandRegistry, IChatTracker, IInputToolbarRegistryFactory, InputToolbarRegistry, MultiChatPanel } from '@jupyter/chat';
|
|
3
|
+
import { ICommandPalette, IThemeManager, showErrorMessage, WidgetTracker } from '@jupyterlab/apputils';
|
|
4
4
|
import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
5
5
|
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
6
6
|
import { INotebookTracker } from '@jupyterlab/notebook';
|
|
@@ -14,12 +14,13 @@ import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
|
|
|
14
14
|
import { PromiseDelegate, UUID } from '@lumino/coreutils';
|
|
15
15
|
import { DisposableSet } from '@lumino/disposable';
|
|
16
16
|
import { AgentManagerFactory } from './agent';
|
|
17
|
+
import { RenderedMessageOutputAreaCompat } from './rendered-message-outputarea';
|
|
17
18
|
import { ClearCommandProvider } from './chat-commands/clear';
|
|
18
19
|
import { SkillsCommandProvider } from './chat-commands/skills';
|
|
19
20
|
import { ProviderRegistry } from './providers/provider-registry';
|
|
20
21
|
import { ApprovalButtons } from './approval-buttons';
|
|
21
|
-
import {
|
|
22
|
-
import { CommandIds, IAgentManagerFactory, IProviderRegistry, IToolRegistry, ISkillRegistry, SECRETS_NAMESPACE, IAISettingsModel,
|
|
22
|
+
import { ChatModelHandler } from './chat-model-handler';
|
|
23
|
+
import { CommandIds, IAgentManagerFactory, IProviderRegistry, IToolRegistry, ISkillRegistry, SECRETS_NAMESPACE, IAISettingsModel, IChatModelHandler, IDiffManager } from './tokens';
|
|
23
24
|
import { anthropicProvider, googleProvider, mistralProvider, openaiProvider, genericProvider } from './providers/built-in-providers';
|
|
24
25
|
import { AICompletionProvider } from './completion';
|
|
25
26
|
import { clearItem, createModelSelectItem, createToolSelectItem, stopItem, CompletionStatusWidget, TokenUsageWidget } from './components';
|
|
@@ -29,8 +30,48 @@ import { DiffManager } from './diff-manager';
|
|
|
29
30
|
import { ToolRegistry } from './tools/tool-registry';
|
|
30
31
|
import { createDiscoverCommandsTool, createExecuteCommandTool } from './tools/commands';
|
|
31
32
|
import { createDiscoverSkillsTool, createLoadSkillTool } from './tools/skills';
|
|
33
|
+
import { createBrowserFetchTool } from './tools/web';
|
|
32
34
|
import { AISettingsWidget } from './widgets/ai-settings';
|
|
33
35
|
import { MainAreaChat } from './widgets/main-area-chat';
|
|
36
|
+
var Private;
|
|
37
|
+
(function (Private) {
|
|
38
|
+
let aiSecretsToken = null;
|
|
39
|
+
function setAISecretsToken(token) {
|
|
40
|
+
aiSecretsToken = token;
|
|
41
|
+
}
|
|
42
|
+
Private.setAISecretsToken = setAISecretsToken;
|
|
43
|
+
function createAISecretsAccess(secretsManager) {
|
|
44
|
+
return {
|
|
45
|
+
get isAvailable() {
|
|
46
|
+
return !!(aiSecretsToken && secretsManager);
|
|
47
|
+
},
|
|
48
|
+
async get(id) {
|
|
49
|
+
if (!aiSecretsToken || !secretsManager) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const secret = await secretsManager.get(aiSecretsToken, SECRETS_NAMESPACE, id);
|
|
53
|
+
return secret?.value;
|
|
54
|
+
},
|
|
55
|
+
async set(id, value) {
|
|
56
|
+
if (!aiSecretsToken || !secretsManager) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
await secretsManager.set(aiSecretsToken, SECRETS_NAMESPACE, id, {
|
|
60
|
+
namespace: SECRETS_NAMESPACE,
|
|
61
|
+
id,
|
|
62
|
+
value
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
async attach(id, input, callback) {
|
|
66
|
+
if (!aiSecretsToken || !secretsManager) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await secretsManager.attach(aiSecretsToken, SECRETS_NAMESPACE, id, input, callback);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
Private.createAISecretsAccess = createAISecretsAccess;
|
|
74
|
+
})(Private || (Private = {}));
|
|
34
75
|
/**
|
|
35
76
|
* Provider registry plugin
|
|
36
77
|
*/
|
|
@@ -143,21 +184,27 @@ const skillsCommandPlugin = {
|
|
|
143
184
|
}
|
|
144
185
|
};
|
|
145
186
|
/**
|
|
146
|
-
* The chat model
|
|
187
|
+
* The chat model handler.
|
|
147
188
|
*/
|
|
148
|
-
const
|
|
149
|
-
id: '@jupyterlite/ai:chat-model-
|
|
150
|
-
description: '
|
|
189
|
+
const chatModelHandler = {
|
|
190
|
+
id: '@jupyterlite/ai:chat-model-handler',
|
|
191
|
+
description: 'A handler to create current chat model',
|
|
151
192
|
autoStart: true,
|
|
152
|
-
requires: [
|
|
193
|
+
requires: [
|
|
194
|
+
IAISettingsModel,
|
|
195
|
+
IAgentManagerFactory,
|
|
196
|
+
IDocumentManager,
|
|
197
|
+
IRenderMimeRegistry
|
|
198
|
+
],
|
|
153
199
|
optional: [IProviderRegistry, IToolRegistry, ITranslator],
|
|
154
|
-
provides:
|
|
155
|
-
activate: (app, settingsModel, agentManagerFactory, docManager, providerRegistry, toolRegistry, translator) => {
|
|
200
|
+
provides: IChatModelHandler,
|
|
201
|
+
activate: (app, settingsModel, agentManagerFactory, docManager, rmRegistry, providerRegistry, toolRegistry, translator) => {
|
|
156
202
|
const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
|
|
157
|
-
return new
|
|
203
|
+
return new ChatModelHandler({
|
|
158
204
|
settingsModel,
|
|
159
205
|
agentManagerFactory,
|
|
160
206
|
docManager,
|
|
207
|
+
rmRegistry,
|
|
161
208
|
providerRegistry,
|
|
162
209
|
toolRegistry,
|
|
163
210
|
trans
|
|
@@ -171,10 +218,11 @@ const plugin = {
|
|
|
171
218
|
id: '@jupyterlite/ai:plugin',
|
|
172
219
|
description: 'AI in JupyterLab',
|
|
173
220
|
autoStart: true,
|
|
221
|
+
provides: IChatTracker,
|
|
174
222
|
requires: [
|
|
175
223
|
IRenderMimeRegistry,
|
|
176
224
|
IInputToolbarRegistryFactory,
|
|
177
|
-
|
|
225
|
+
IChatModelHandler,
|
|
178
226
|
IAISettingsModel,
|
|
179
227
|
IChatCommandRegistry
|
|
180
228
|
],
|
|
@@ -185,7 +233,7 @@ const plugin = {
|
|
|
185
233
|
INotebookTracker,
|
|
186
234
|
ITranslator
|
|
187
235
|
],
|
|
188
|
-
activate: (app, rmRegistry, inputToolbarFactory,
|
|
236
|
+
activate: (app, rmRegistry, inputToolbarFactory, modelHandler, settingsModel, chatCommandRegistry, themeManager, restorer, labShell, notebookTracker, translator) => {
|
|
189
237
|
const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
|
|
190
238
|
// Create attachment opener registry to handle file attachments
|
|
191
239
|
const attachmentOpenerRegistry = new AttachmentOpenerRegistry();
|
|
@@ -195,6 +243,11 @@ const plugin = {
|
|
|
195
243
|
attachmentOpenerRegistry.set('notebook', attachment => {
|
|
196
244
|
app.commands.execute('docmanager:open', { path: attachment.value });
|
|
197
245
|
});
|
|
246
|
+
const openSettings = () => {
|
|
247
|
+
if (app.commands.hasCommand(CommandIds.openSettings)) {
|
|
248
|
+
void app.commands.execute(CommandIds.openSettings);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
198
251
|
// Create ActiveCellManager if notebook tracker is available, and add it to the
|
|
199
252
|
// model registry.
|
|
200
253
|
let activeCellManager;
|
|
@@ -204,7 +257,10 @@ const plugin = {
|
|
|
204
257
|
shell: app.shell
|
|
205
258
|
});
|
|
206
259
|
}
|
|
207
|
-
|
|
260
|
+
modelHandler.activeCellManager = activeCellManager;
|
|
261
|
+
// Creating the tracker for the chat widgets
|
|
262
|
+
const namespace = 'ai-chat';
|
|
263
|
+
const tracker = new WidgetTracker({ namespace });
|
|
208
264
|
// Create chat panel with drag/drop functionality
|
|
209
265
|
const chatPanel = new MultiChatPanel({
|
|
210
266
|
rmRegistry,
|
|
@@ -212,19 +268,35 @@ const plugin = {
|
|
|
212
268
|
inputToolbarFactory,
|
|
213
269
|
attachmentOpenerRegistry,
|
|
214
270
|
chatCommandRegistry,
|
|
215
|
-
createModel: async (
|
|
216
|
-
|
|
271
|
+
createModel: async (provider) => {
|
|
272
|
+
if (!provider) {
|
|
273
|
+
provider = settingsModel.getDefaultProvider()?.id;
|
|
274
|
+
if (!provider) {
|
|
275
|
+
showErrorMessage('Error creating chat', 'Please set up a provider');
|
|
276
|
+
openSettings();
|
|
277
|
+
return {};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
let name = settingsModel.getProvider(provider)?.name ?? UUID.uuid4();
|
|
281
|
+
const modelName = name;
|
|
282
|
+
const existingName = new Set(chatPanel.getLoadedModelNames());
|
|
283
|
+
tracker.forEach(widget => existingName.add(widget.model.name));
|
|
284
|
+
let i = 1;
|
|
285
|
+
while (existingName.has(name)) {
|
|
286
|
+
name = `${modelName}-${i}`;
|
|
287
|
+
i += 1;
|
|
288
|
+
}
|
|
289
|
+
const model = modelHandler.createModel(name, provider);
|
|
217
290
|
return { model };
|
|
218
291
|
},
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
return false;
|
|
292
|
+
getChatNames: async () => {
|
|
293
|
+
const names = {};
|
|
294
|
+
settingsModel.config.providers.forEach(provider => {
|
|
295
|
+
names[provider.name] = provider.id;
|
|
296
|
+
});
|
|
297
|
+
return names;
|
|
227
298
|
},
|
|
299
|
+
renameChat: true,
|
|
228
300
|
openInMain: (name) => app.commands.execute(CommandIds.moveChat, {
|
|
229
301
|
area: 'main',
|
|
230
302
|
name
|
|
@@ -234,30 +306,53 @@ const plugin = {
|
|
|
234
306
|
chatPanel.title.icon = chatIcon;
|
|
235
307
|
chatPanel.title.caption = trans.__('Chat with AI assistant');
|
|
236
308
|
chatPanel.toolbar.addItem('spacer', Toolbar.createSpacerItem());
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
309
|
+
const addSettingsButton = () => {
|
|
310
|
+
chatPanel.toolbar.addItem('settings', new ToolbarButton({
|
|
311
|
+
icon: settingsIcon,
|
|
312
|
+
onClick: openSettings,
|
|
313
|
+
tooltip: trans.__('Open AI Settings')
|
|
314
|
+
}));
|
|
315
|
+
};
|
|
316
|
+
if (app.commands.hasCommand(CommandIds.openSettings)) {
|
|
317
|
+
addSettingsButton();
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
const disconnectSettingsButtonListener = () => {
|
|
321
|
+
app.commands.commandChanged.disconnect(onCommandChanged);
|
|
322
|
+
chatPanel.disposed.disconnect(disconnectSettingsButtonListener);
|
|
323
|
+
};
|
|
324
|
+
const onCommandChanged = (_, args) => {
|
|
325
|
+
if (args.id === CommandIds.openSettings && args.type === 'added') {
|
|
326
|
+
disconnectSettingsButtonListener();
|
|
327
|
+
addSettingsButton();
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
app.commands.commandChanged.connect(onCommandChanged);
|
|
331
|
+
chatPanel.disposed.connect(disconnectSettingsButtonListener);
|
|
332
|
+
}
|
|
333
|
+
let tokenUsageWidget = null;
|
|
334
|
+
chatPanel.chatOpened.connect((_, widget) => {
|
|
335
|
+
const model = widget.model;
|
|
247
336
|
// Add the widget to the tracker.
|
|
248
337
|
tracker.add(widget);
|
|
338
|
+
function saveTracker() {
|
|
339
|
+
tracker.save(widget);
|
|
340
|
+
}
|
|
249
341
|
// Update the tracker if the model name changed.
|
|
250
|
-
model.nameChanged.connect(
|
|
342
|
+
model.nameChanged.connect(saveTracker);
|
|
251
343
|
// Update the tracker if the active provider changed.
|
|
252
|
-
model.agentManager.activeProviderChanged.connect(
|
|
253
|
-
|
|
344
|
+
model.agentManager.activeProviderChanged.connect(saveTracker);
|
|
345
|
+
// Update the token usage widget.
|
|
346
|
+
tokenUsageWidget?.dispose();
|
|
347
|
+
tokenUsageWidget = new TokenUsageWidget({
|
|
254
348
|
tokenUsageChanged: model.tokenUsageChanged,
|
|
255
349
|
settingsModel,
|
|
256
350
|
initialTokenUsage: model.agentManager.tokenUsage,
|
|
257
351
|
translator: trans
|
|
258
352
|
});
|
|
259
|
-
|
|
260
|
-
|
|
353
|
+
chatPanel.current?.toolbar.insertBefore('markRead', 'token-usage', tokenUsageWidget);
|
|
354
|
+
// Listen for writers change to display the stop button.
|
|
355
|
+
function writersChanged(_, writers) {
|
|
261
356
|
// Check if AI is currently writing (streaming)
|
|
262
357
|
const aiWriting = writers.some(writer => writer.user.username === 'ai-assistant');
|
|
263
358
|
if (aiWriting) {
|
|
@@ -268,23 +363,28 @@ const plugin = {
|
|
|
268
363
|
widget.inputToolbarRegistry?.hide('stop');
|
|
269
364
|
widget.inputToolbarRegistry?.show('send');
|
|
270
365
|
}
|
|
271
|
-
}
|
|
366
|
+
}
|
|
367
|
+
model.writersChanged?.connect(writersChanged);
|
|
272
368
|
// Associate an approval buttons object to the chat.
|
|
273
369
|
const approvalButton = new ApprovalButtons({
|
|
274
370
|
chatPanel: widget,
|
|
275
371
|
agentManager: model.agentManager
|
|
276
372
|
});
|
|
373
|
+
// Temporary compat: keep output-area CSS context for MIME renderers
|
|
374
|
+
// until jupyter-chat provides it natively.
|
|
375
|
+
const outputAreaCompat = new RenderedMessageOutputAreaCompat({
|
|
376
|
+
chatPanel: widget
|
|
377
|
+
});
|
|
277
378
|
widget.disposed.connect(() => {
|
|
379
|
+
model.nameChanged.disconnect(saveTracker);
|
|
380
|
+
model.agentManager.activeProviderChanged.disconnect(saveTracker);
|
|
381
|
+
model.writersChanged?.disconnect(writersChanged);
|
|
278
382
|
// Dispose of the approval buttons widget when the chat is disposed.
|
|
279
383
|
approvalButton.dispose();
|
|
280
|
-
|
|
281
|
-
modelRegistry.remove(model.name);
|
|
384
|
+
outputAreaCompat.dispose();
|
|
282
385
|
});
|
|
283
386
|
});
|
|
284
387
|
app.shell.add(chatPanel, 'left', { rank: 1000 });
|
|
285
|
-
// Creating the tracker for the document
|
|
286
|
-
const namespace = 'ai-chat';
|
|
287
|
-
const tracker = new WidgetTracker({ namespace });
|
|
288
388
|
if (restorer) {
|
|
289
389
|
restorer.add(chatPanel, chatPanel.id);
|
|
290
390
|
void restorer.restore(tracker, {
|
|
@@ -302,12 +402,12 @@ const plugin = {
|
|
|
302
402
|
}
|
|
303
403
|
// Create a chat with default provider at startup.
|
|
304
404
|
app.restored.then(() => {
|
|
305
|
-
if (!
|
|
306
|
-
settingsModel.config.defaultProvider) {
|
|
405
|
+
if (!tracker.size && settingsModel.config.defaultProvider) {
|
|
307
406
|
app.commands.execute(CommandIds.openChat);
|
|
308
407
|
}
|
|
309
408
|
});
|
|
310
|
-
registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry, inputToolbarFactory, settingsModel, chatCommandRegistry, tracker,
|
|
409
|
+
registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry, inputToolbarFactory, settingsModel, chatCommandRegistry, tracker, modelHandler, trans, themeManager, labShell);
|
|
410
|
+
return tracker;
|
|
311
411
|
}
|
|
312
412
|
};
|
|
313
413
|
function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry, inputToolbarFactory, settingsModel, chatCommandRegistry, tracker, modelRegistry, trans, themeManager, labShell) {
|
|
@@ -373,13 +473,16 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
|
|
|
373
473
|
app.shell.add(widget, 'main');
|
|
374
474
|
// Add the widget to the tracker.
|
|
375
475
|
tracker.add(widget);
|
|
476
|
+
function saveTracker() {
|
|
477
|
+
tracker.save(widget);
|
|
478
|
+
}
|
|
376
479
|
// Update the tracker if the model name changed.
|
|
377
|
-
model.nameChanged.connect(
|
|
480
|
+
model.nameChanged.connect(saveTracker);
|
|
378
481
|
// Update the tracker if the active provider changed.
|
|
379
|
-
model.agentManager.activeProviderChanged.connect(
|
|
380
|
-
// Remove the model from the registry when the widget is disposed.
|
|
482
|
+
model.agentManager.activeProviderChanged.connect(saveTracker);
|
|
381
483
|
widget.disposed.connect(() => {
|
|
382
|
-
|
|
484
|
+
model.nameChanged.disconnect(saveTracker);
|
|
485
|
+
model.agentManager.activeProviderChanged.disconnect(saveTracker);
|
|
383
486
|
});
|
|
384
487
|
};
|
|
385
488
|
commands.addCommand(CommandIds.openChat, {
|
|
@@ -387,11 +490,23 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
|
|
|
387
490
|
execute: async (args) => {
|
|
388
491
|
const area = args.area === 'main' ? 'main' : 'side';
|
|
389
492
|
const provider = args.provider ?? undefined;
|
|
390
|
-
|
|
391
|
-
|
|
493
|
+
let name = args.name ?? undefined;
|
|
494
|
+
let providerConfig = undefined;
|
|
495
|
+
if (provider) {
|
|
496
|
+
providerConfig = settingsModel.getProvider(provider);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
providerConfig = settingsModel.getDefaultProvider();
|
|
500
|
+
}
|
|
501
|
+
// Do not open the chat if the provider in args does not exists in settings or
|
|
502
|
+
// if there is no default provider.
|
|
503
|
+
if (!providerConfig) {
|
|
392
504
|
return false;
|
|
393
505
|
}
|
|
394
|
-
|
|
506
|
+
if (!name) {
|
|
507
|
+
name = providerConfig.name;
|
|
508
|
+
}
|
|
509
|
+
const model = modelRegistry.createModel(name, provider);
|
|
395
510
|
if (!model) {
|
|
396
511
|
return false;
|
|
397
512
|
}
|
|
@@ -399,7 +514,7 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
|
|
|
399
514
|
openInMain(model);
|
|
400
515
|
}
|
|
401
516
|
else {
|
|
402
|
-
chatPanel.
|
|
517
|
+
chatPanel.open({ model });
|
|
403
518
|
}
|
|
404
519
|
return true;
|
|
405
520
|
},
|
|
@@ -436,7 +551,14 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
|
|
|
436
551
|
console.error('Error while moving the chat to main area: the name has not been provided');
|
|
437
552
|
return false;
|
|
438
553
|
}
|
|
439
|
-
|
|
554
|
+
let previousWidget;
|
|
555
|
+
let previousModel;
|
|
556
|
+
tracker.forEach(widget => {
|
|
557
|
+
if (widget.model.name === args.name) {
|
|
558
|
+
previousWidget = widget;
|
|
559
|
+
previousModel = widget.model;
|
|
560
|
+
}
|
|
561
|
+
});
|
|
440
562
|
if (!previousModel) {
|
|
441
563
|
console.error('Error while moving the chat to main area: there is no reference model');
|
|
442
564
|
return false;
|
|
@@ -455,8 +577,10 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
|
|
|
455
577
|
// model. The previous is intended to be disposed anyway.
|
|
456
578
|
previousModel.name = UUID.uuid4();
|
|
457
579
|
// Create a new model by duplicating the previous model attributes.
|
|
458
|
-
const model = modelRegistry.createModel(args.name, previousModel
|
|
459
|
-
previousModel?.messages.forEach(message =>
|
|
580
|
+
const model = modelRegistry.createModel(args.name, previousModel.agentManager.activeProvider, previousModel.agentManager.tokenUsage);
|
|
581
|
+
previousModel?.messages.forEach(message => {
|
|
582
|
+
model?.messageAdded({ ...message.content });
|
|
583
|
+
});
|
|
460
584
|
// Wait (with timeout) for the tracker to have updated the previous widget.
|
|
461
585
|
const status = await Promise.any([
|
|
462
586
|
trackerUpdated.promise,
|
|
@@ -472,13 +596,9 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
|
|
|
472
596
|
openInMain(model);
|
|
473
597
|
}
|
|
474
598
|
else {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
current.model.name === previousModel.name) {
|
|
479
|
-
current.dispose();
|
|
480
|
-
}
|
|
481
|
-
chatPanel.addChat({ model });
|
|
599
|
+
previousWidget?.dispose();
|
|
600
|
+
previousModel.dispose();
|
|
601
|
+
chatPanel.open({ model });
|
|
482
602
|
}
|
|
483
603
|
return true;
|
|
484
604
|
},
|
|
@@ -489,7 +609,7 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
|
|
|
489
609
|
area: {
|
|
490
610
|
type: 'string',
|
|
491
611
|
enum: ['main', 'side'],
|
|
492
|
-
description: trans.__('The
|
|
612
|
+
description: trans.__('The area to move the chat to')
|
|
493
613
|
},
|
|
494
614
|
name: {
|
|
495
615
|
type: 'string',
|
|
@@ -503,57 +623,78 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
|
|
|
503
623
|
}
|
|
504
624
|
}
|
|
505
625
|
/**
|
|
506
|
-
* A plugin to provide the
|
|
626
|
+
* A plugin to provide the agent manager factory and completion provider.
|
|
627
|
+
* These objects require the secrets manager token with the same namespace.
|
|
628
|
+
*/
|
|
629
|
+
const agentManagerFactory = SecretsManager.sign(SECRETS_NAMESPACE, token => {
|
|
630
|
+
Private.setAISecretsToken(token);
|
|
631
|
+
return {
|
|
632
|
+
id: SECRETS_NAMESPACE,
|
|
633
|
+
description: 'Provide the AI agent manager',
|
|
634
|
+
autoStart: true,
|
|
635
|
+
provides: IAgentManagerFactory,
|
|
636
|
+
requires: [IAISettingsModel, IProviderRegistry],
|
|
637
|
+
optional: [ISkillRegistry, ICompletionProviderManager, ISecretsManager],
|
|
638
|
+
activate: (app, settingsModel, providerRegistry, skillRegistry, completionManager, secretsManager) => {
|
|
639
|
+
const agentManagerFactory = new AgentManagerFactory({
|
|
640
|
+
settingsModel,
|
|
641
|
+
skillRegistry,
|
|
642
|
+
secretsManager,
|
|
643
|
+
token
|
|
644
|
+
});
|
|
645
|
+
// Build the completion provider
|
|
646
|
+
if (completionManager) {
|
|
647
|
+
const completionProvider = new AICompletionProvider({
|
|
648
|
+
settingsModel,
|
|
649
|
+
providerRegistry,
|
|
650
|
+
secretsManager,
|
|
651
|
+
token
|
|
652
|
+
});
|
|
653
|
+
completionManager.registerInlineProvider(completionProvider);
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
console.info('Completion provider manager not available, skipping AI completion setup');
|
|
657
|
+
}
|
|
658
|
+
return agentManagerFactory;
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
});
|
|
662
|
+
/**
|
|
663
|
+
* AI settings panel plugin.
|
|
507
664
|
*/
|
|
508
|
-
const
|
|
509
|
-
id:
|
|
510
|
-
description: 'Provide the AI
|
|
665
|
+
const settingsPanelPlugin = {
|
|
666
|
+
id: '@jupyterlite/ai:settings-panel',
|
|
667
|
+
description: 'Provide the AI settings panel',
|
|
511
668
|
autoStart: true,
|
|
512
|
-
|
|
513
|
-
requires: [IAISettingsModel, IProviderRegistry],
|
|
669
|
+
requires: [IAISettingsModel, IAgentManagerFactory, IProviderRegistry],
|
|
514
670
|
optional: [
|
|
515
|
-
ISkillRegistry,
|
|
516
671
|
ICommandPalette,
|
|
517
|
-
ICompletionProviderManager,
|
|
518
672
|
ILayoutRestorer,
|
|
519
673
|
ISecretsManager,
|
|
520
674
|
IThemeManager,
|
|
521
675
|
ITranslator
|
|
522
676
|
],
|
|
523
|
-
activate: (app, settingsModel,
|
|
677
|
+
activate: (app, settingsModel, agentManagerFactory, providerRegistry, palette, restorer, secretsManager, themeManager, translator) => {
|
|
524
678
|
const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
|
|
525
|
-
const
|
|
526
|
-
settingsModel,
|
|
527
|
-
skillRegistry,
|
|
528
|
-
secretsManager,
|
|
529
|
-
token
|
|
530
|
-
});
|
|
531
|
-
// Build the settings panel
|
|
679
|
+
const secretsAccess = Private.createAISecretsAccess(secretsManager);
|
|
532
680
|
const settingsWidget = new AISettingsWidget({
|
|
533
681
|
settingsModel,
|
|
534
682
|
agentManagerFactory,
|
|
535
683
|
themeManager,
|
|
536
684
|
providerRegistry,
|
|
537
|
-
|
|
538
|
-
token,
|
|
685
|
+
secretsAccess,
|
|
539
686
|
trans
|
|
540
687
|
});
|
|
541
|
-
settingsWidget.id = 'jupyterlite-ai-settings';
|
|
542
688
|
settingsWidget.title.icon = settingsIcon;
|
|
543
689
|
settingsWidget.title.iconClass = 'jp-ai-settings-icon';
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
completionManager.registerInlineProvider(completionProvider);
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
console.info('Completion provider manager not available, skipping AI completion setup');
|
|
556
|
-
}
|
|
690
|
+
const open = () => {
|
|
691
|
+
let widget = Array.from(app.shell.widgets('main')).find(w => w.id === 'jupyterlite-ai-settings');
|
|
692
|
+
if (!widget) {
|
|
693
|
+
widget = settingsWidget;
|
|
694
|
+
app.shell.add(widget, 'main');
|
|
695
|
+
}
|
|
696
|
+
app.shell.activateById(widget.id);
|
|
697
|
+
};
|
|
557
698
|
if (restorer) {
|
|
558
699
|
restorer.add(settingsWidget, settingsWidget.id);
|
|
559
700
|
}
|
|
@@ -563,31 +704,20 @@ const agentManagerFactory = SecretsManager.sign(SECRETS_NAMESPACE, token => ({
|
|
|
563
704
|
icon: settingsIcon,
|
|
564
705
|
iconClass: 'jp-ai-settings-icon',
|
|
565
706
|
execute: () => {
|
|
566
|
-
|
|
567
|
-
let widget = Array.from(app.shell.widgets('main')).find(w => w.id === 'jupyterlite-ai-settings');
|
|
568
|
-
if (!widget && settingsWidget) {
|
|
569
|
-
// Use the pre-created widget
|
|
570
|
-
widget = settingsWidget;
|
|
571
|
-
app.shell.add(widget, 'main');
|
|
572
|
-
}
|
|
573
|
-
if (widget) {
|
|
574
|
-
app.shell.activateById(widget.id);
|
|
575
|
-
}
|
|
707
|
+
open();
|
|
576
708
|
},
|
|
577
709
|
describedBy: {
|
|
578
710
|
args: {}
|
|
579
711
|
}
|
|
580
712
|
});
|
|
581
|
-
// Add to command palette if available
|
|
582
713
|
if (palette) {
|
|
583
714
|
palette.addItem({
|
|
584
715
|
command: CommandIds.openSettings,
|
|
585
716
|
category: trans.__('AI Assistant')
|
|
586
717
|
});
|
|
587
718
|
}
|
|
588
|
-
return agentManagerFactory;
|
|
589
719
|
}
|
|
590
|
-
}
|
|
720
|
+
};
|
|
591
721
|
/**
|
|
592
722
|
* Built-in completion providers plugin
|
|
593
723
|
*/
|
|
@@ -643,6 +773,7 @@ const toolRegistry = {
|
|
|
643
773
|
const executeCommandTool = createExecuteCommandTool(app.commands, settingsModel);
|
|
644
774
|
toolRegistry.add('discover_commands', discoverCommandsTool);
|
|
645
775
|
toolRegistry.add('execute_command', executeCommandTool);
|
|
776
|
+
toolRegistry.add('browser_fetch', createBrowserFetchTool());
|
|
646
777
|
if (skillRegistry) {
|
|
647
778
|
toolRegistry.add('discover_skills', createDiscoverSkillsTool(skillRegistry));
|
|
648
779
|
toolRegistry.add('load_skill', createLoadSkillTool(skillRegistry));
|
|
@@ -658,13 +789,13 @@ const inputToolbarFactory = {
|
|
|
658
789
|
description: 'The input toolbar registry plugin.',
|
|
659
790
|
autoStart: true,
|
|
660
791
|
provides: IInputToolbarRegistryFactory,
|
|
661
|
-
requires: [IAISettingsModel, IToolRegistry],
|
|
792
|
+
requires: [IAISettingsModel, IToolRegistry, IProviderRegistry],
|
|
662
793
|
optional: [ITranslator],
|
|
663
|
-
activate: (app, settingsModel, toolRegistry, translator) => {
|
|
794
|
+
activate: (app, settingsModel, toolRegistry, providerRegistry, translator) => {
|
|
664
795
|
const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
|
|
665
796
|
const stopButton = stopItem(trans);
|
|
666
797
|
const clearButton = clearItem(trans);
|
|
667
|
-
const toolSelectButton = createToolSelectItem(toolRegistry, settingsModel.config.toolsEnabled, trans);
|
|
798
|
+
const toolSelectButton = createToolSelectItem(toolRegistry, settingsModel, providerRegistry, settingsModel.config.toolsEnabled, trans);
|
|
668
799
|
const modelSelectButton = createModelSelectItem(settingsModel, trans);
|
|
669
800
|
return {
|
|
670
801
|
create() {
|
|
@@ -828,10 +959,11 @@ export default [
|
|
|
828
959
|
clearCommandPlugin,
|
|
829
960
|
skillRegistryPlugin,
|
|
830
961
|
skillsCommandPlugin,
|
|
831
|
-
|
|
962
|
+
chatModelHandler,
|
|
832
963
|
plugin,
|
|
833
964
|
toolRegistry,
|
|
834
965
|
agentManagerFactory,
|
|
966
|
+
settingsPanelPlugin,
|
|
835
967
|
inputToolbarFactory,
|
|
836
968
|
completionStatus,
|
|
837
969
|
skillsPlugin
|
|
@@ -41,6 +41,8 @@ export interface IAIConfig {
|
|
|
41
41
|
sendWithShiftEnter: boolean;
|
|
42
42
|
showTokenUsage: boolean;
|
|
43
43
|
commandsRequiringApproval: string[];
|
|
44
|
+
commandsAutoRenderMimeBundles: string[];
|
|
45
|
+
trustedMimeTypesForAutoRender: string[];
|
|
44
46
|
showCellDiff: boolean;
|
|
45
47
|
showFileDiff: boolean;
|
|
46
48
|
diffDisplayMode: 'split' | 'unified';
|
|
@@ -33,6 +33,8 @@ export class AISettingsModel extends VDomModel {
|
|
|
33
33
|
'runmenu:run-all',
|
|
34
34
|
'jupyterlab-ai-commands:run-cell'
|
|
35
35
|
],
|
|
36
|
+
commandsAutoRenderMimeBundles: ['jupyterlab-ai-commands:execute-in-kernel'],
|
|
37
|
+
trustedMimeTypesForAutoRender: ['text/html'],
|
|
36
38
|
systemPrompt: `You are Jupyternaut, an AI coding assistant built specifically for the JupyterLab environment.
|
|
37
39
|
|
|
38
40
|
## Your Core Mission
|