@jupyterlite/ai 0.13.0 → 0.15.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 (53) hide show
  1. package/lib/agent.d.ts +28 -102
  2. package/lib/agent.js +150 -22
  3. package/lib/chat-model-handler.d.ts +9 -11
  4. package/lib/chat-model-handler.js +9 -4
  5. package/lib/chat-model.d.ts +84 -13
  6. package/lib/chat-model.js +208 -136
  7. package/lib/completion/completion-provider.d.ts +2 -3
  8. package/lib/components/completion-status.d.ts +2 -2
  9. package/lib/components/model-select.d.ts +3 -3
  10. package/lib/components/save-button.d.ts +31 -0
  11. package/lib/components/save-button.js +41 -0
  12. package/lib/components/token-usage-display.d.ts +2 -3
  13. package/lib/components/tool-select.d.ts +3 -4
  14. package/lib/diff-manager.d.ts +2 -3
  15. package/lib/index.d.ts +2 -4
  16. package/lib/index.js +306 -78
  17. package/lib/models/settings-model.d.ts +11 -53
  18. package/lib/models/settings-model.js +37 -22
  19. package/lib/providers/built-in-providers.js +29 -54
  20. package/lib/tokens.d.ts +351 -26
  21. package/lib/tokens.js +11 -6
  22. package/lib/tools/commands.d.ts +2 -3
  23. package/lib/tools/commands.js +58 -20
  24. package/lib/widgets/ai-settings.d.ts +6 -13
  25. package/lib/widgets/ai-settings.js +18 -48
  26. package/lib/widgets/main-area-chat.d.ts +2 -3
  27. package/lib/widgets/main-area-chat.js +9 -9
  28. package/lib/widgets/provider-config-dialog.d.ts +1 -2
  29. package/lib/widgets/provider-config-dialog.js +21 -37
  30. package/package.json +15 -9
  31. package/schema/settings-model.json +7 -1
  32. package/src/agent.ts +211 -149
  33. package/src/chat-model-handler.ts +25 -21
  34. package/src/chat-model.ts +304 -196
  35. package/src/completion/completion-provider.ts +7 -4
  36. package/src/components/completion-status.tsx +3 -3
  37. package/src/components/model-select.tsx +4 -3
  38. package/src/components/save-button.tsx +84 -0
  39. package/src/components/token-usage-display.tsx +2 -3
  40. package/src/components/tool-select.tsx +10 -4
  41. package/src/diff-manager.ts +4 -4
  42. package/src/index.ts +451 -151
  43. package/src/models/settings-model.ts +45 -88
  44. package/src/providers/built-in-providers.ts +29 -54
  45. package/src/tokens.ts +418 -35
  46. package/src/tools/commands.ts +97 -32
  47. package/src/widgets/ai-settings.tsx +45 -83
  48. package/src/widgets/main-area-chat.ts +15 -12
  49. package/src/widgets/provider-config-dialog.tsx +59 -64
  50. package/style/base.css +17 -195
  51. package/lib/approval-buttons.d.ts +0 -49
  52. package/lib/approval-buttons.js +0 -79
  53. package/src/approval-buttons.ts +0 -115
@@ -1,10 +1,8 @@
1
- import { AbstractChatModel, IActiveCellManager, IChatContext, INewMessage, IUser } from '@jupyter/chat';
1
+ import { AbstractChatModel, IActiveCellManager, IAttachment, IChatContext, IMessageContent, INewMessage, IUser } from '@jupyter/chat';
2
2
  import { IDocumentManager } from '@jupyterlab/docmanager';
3
- import { TranslationBundle } from '@jupyterlab/translation';
3
+ import { Contents } from '@jupyterlab/services';
4
4
  import { ISignal } from '@lumino/signaling';
5
- import { AgentManager } from './agent';
6
- import { AISettingsModel } from './models/settings-model';
7
- import { ITokenUsage } from './tokens';
5
+ import type { IAgentManager, IAISettingsModel, ITokenUsage } from './tokens';
8
6
  /**
9
7
  * AI Chat Model implementation that provides chat functionality tool integration,
10
8
  * and MCP server support.
@@ -20,6 +18,15 @@ export declare class AIChatModel extends AbstractChatModel {
20
18
  */
21
19
  get name(): string;
22
20
  set name(value: string);
21
+ /**
22
+ * Whether to save the chat automatically.
23
+ */
24
+ get autosave(): boolean;
25
+ set autosave(value: boolean);
26
+ /**
27
+ * A signal emitting when the autosave flag changed.
28
+ */
29
+ get autosaveChanged(): ISignal<AIChatModel, boolean>;
23
30
  /**
24
31
  * A signal emitting when the chat name has changed.
25
32
  */
@@ -31,11 +38,19 @@ export declare class AIChatModel extends AbstractChatModel {
31
38
  /**
32
39
  * A signal emitting when the token usage changed.
33
40
  */
34
- get tokenUsageChanged(): ISignal<AgentManager, ITokenUsage>;
41
+ get tokenUsageChanged(): ISignal<IAgentManager, ITokenUsage>;
35
42
  /**
36
43
  * Get the agent manager associated to the model.
37
44
  */
38
- get agentManager(): AgentManager;
45
+ get agentManager(): IAgentManager;
46
+ /**
47
+ * Whether save/restore is available.
48
+ */
49
+ get saveAvailable(): boolean;
50
+ /**
51
+ * Dispose of the model.
52
+ */
53
+ dispose(): void;
39
54
  /**
40
55
  * Creates a chat context for the current conversation.
41
56
  */
@@ -57,6 +72,21 @@ export declare class AIChatModel extends AbstractChatModel {
57
72
  * @param message The user message to send
58
73
  */
59
74
  sendMessage(message: INewMessage): Promise<void>;
75
+ /**
76
+ * Save the chat as json file.
77
+ */
78
+ save: () => Promise<void>;
79
+ /**
80
+ * Restore the chat from a json file.
81
+ *
82
+ * @param silent - Whether a log should be displayed in the console if the
83
+ * restoration is not possible.
84
+ */
85
+ restore: (filepath: string, silent?: boolean) => Promise<boolean>;
86
+ /**
87
+ * Serialize the model for backup
88
+ */
89
+ private _serializeModel;
60
90
  /**
61
91
  * Gets the AI user information for system messages.
62
92
  */
@@ -149,7 +179,10 @@ export declare class AIChatModel extends AbstractChatModel {
149
179
  private _agentManager;
150
180
  private _currentStreamingMessage;
151
181
  private _nameChanged;
152
- private _trans;
182
+ private _contentsManager?;
183
+ private _autosave;
184
+ private _autosaveChanged;
185
+ private _autosaveDebouncer;
153
186
  }
154
187
  /**
155
188
  * Namespace containing types and interfaces for AIChatModel.
@@ -166,11 +199,11 @@ export declare namespace AIChatModel {
166
199
  /**
167
200
  * Settings model for AI configuration
168
201
  */
169
- settingsModel: AISettingsModel;
202
+ settingsModel: IAISettingsModel;
170
203
  /**
171
204
  * Optional agent manager for handling AI agent lifecycle
172
205
  */
173
- agentManager: AgentManager;
206
+ agentManager: IAgentManager;
174
207
  /**
175
208
  * Optional active cell manager for Jupyter integration
176
209
  */
@@ -180,9 +213,13 @@ export declare namespace AIChatModel {
180
213
  */
181
214
  documentManager?: IDocumentManager;
182
215
  /**
183
- * The application language translation bundle.
216
+ * The contents manager.
217
+ */
218
+ contentsManager?: Contents.IManager;
219
+ /**
220
+ * Whether to restore or not the message (default to true)
184
221
  */
185
- trans: TranslationBundle;
222
+ restore?: boolean;
186
223
  }
187
224
  /**
188
225
  * The chat context for toolbar buttons.
@@ -203,6 +240,40 @@ export declare namespace AIChatModel {
203
240
  /**
204
241
  * The agent manager of the chat.
205
242
  */
206
- agentManager: AgentManager;
243
+ agentManager: IAgentManager;
207
244
  }
245
+ /**
246
+ * The exported chat format.
247
+ */
248
+ type ExportedChat = {
249
+ /**
250
+ * Message list (user are only string to avoid duplication).
251
+ */
252
+ messages: IMessageContent<string, string>[];
253
+ /**
254
+ * The user list.
255
+ */
256
+ users: {
257
+ [id: string]: IUser;
258
+ };
259
+ /**
260
+ * The attachments of the chat.
261
+ */
262
+ attachments?: {
263
+ [id: string]: IAttachment;
264
+ };
265
+ /**
266
+ * The metadata associated to the chat.
267
+ */
268
+ metadata?: {
269
+ /**
270
+ * Provider of the chat.
271
+ */
272
+ provider?: string;
273
+ /**
274
+ * Whether the chat is automatically saved.
275
+ */
276
+ autosave?: boolean;
277
+ };
278
+ };
208
279
  }
package/lib/chat-model.js CHANGED
@@ -2,6 +2,7 @@ import { AbstractChatModel } from '@jupyter/chat';
2
2
  import { PathExt } from '@jupyterlab/coreutils';
3
3
  import * as nbformat from '@jupyterlab/nbformat';
4
4
  import { UUID } from '@lumino/coreutils';
5
+ import { Debouncer } from '@lumino/polling';
5
6
  import { Signal } from '@lumino/signaling';
6
7
  import { AI_AVATAR } from './icons';
7
8
  /**
@@ -25,12 +26,12 @@ export class AIChatModel extends AbstractChatModel {
25
26
  this._settingsModel = options.settingsModel;
26
27
  this._user = options.user;
27
28
  this._agentManager = options.agentManager;
28
- this._trans = options.trans;
29
+ this._contentsManager = options.contentsManager;
29
30
  // Listen for agent events
30
31
  this._agentManager.agentEvent.connect(this._onAgentEvent, this);
31
32
  // Listen for settings changes to update chat behavior
32
33
  this._settingsModel.stateChanged.connect(this._onSettingsChanged, this);
33
- this.setReady();
34
+ this._autosaveDebouncer = new Debouncer(this.save, 3000);
34
35
  }
35
36
  /**
36
37
  * Override the getter/setter of the name to add a signal when renaming a chat.
@@ -41,6 +42,37 @@ export class AIChatModel extends AbstractChatModel {
41
42
  set name(value) {
42
43
  super.name = value;
43
44
  this._nameChanged.emit(value);
45
+ if (!this.messages.length) {
46
+ const directory = this._settingsModel.config.chatBackupDirectory;
47
+ const filepath = PathExt.join(directory, `${this.name}.chat`);
48
+ this.restore(filepath, true);
49
+ }
50
+ this.setReady();
51
+ }
52
+ /**
53
+ * Whether to save the chat automatically.
54
+ */
55
+ get autosave() {
56
+ return this._autosave;
57
+ }
58
+ set autosave(value) {
59
+ this._autosave = value;
60
+ this._autosaveChanged.emit(value);
61
+ if (value) {
62
+ this.messagesUpdated.connect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
63
+ this.messageChanged.connect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
64
+ this._autosaveDebouncer.invoke();
65
+ }
66
+ else {
67
+ this.messagesUpdated.disconnect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
68
+ this.messageChanged.disconnect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
69
+ }
70
+ }
71
+ /**
72
+ * A signal emitting when the autosave flag changed.
73
+ */
74
+ get autosaveChanged() {
75
+ return this._autosaveChanged;
44
76
  }
45
77
  /**
46
78
  * A signal emitting when the chat name has changed.
@@ -66,6 +98,19 @@ export class AIChatModel extends AbstractChatModel {
66
98
  get agentManager() {
67
99
  return this._agentManager;
68
100
  }
101
+ /**
102
+ * Whether save/restore is available.
103
+ */
104
+ get saveAvailable() {
105
+ return !!this._contentsManager;
106
+ }
107
+ /**
108
+ * Dispose of the model.
109
+ */
110
+ dispose() {
111
+ this.messagesUpdated.disconnect(this._autosaveDebouncer.invoke, this._autosaveDebouncer);
112
+ super.dispose();
113
+ }
69
114
  /**
70
115
  * Creates a chat context for the current conversation.
71
116
  */
@@ -172,6 +217,135 @@ export class AIChatModel extends AbstractChatModel {
172
217
  this.updateWriters([]);
173
218
  }
174
219
  }
220
+ /**
221
+ * Save the chat as json file.
222
+ */
223
+ save = async () => {
224
+ if (!this._contentsManager) {
225
+ return;
226
+ }
227
+ const directory = this._settingsModel.config.chatBackupDirectory;
228
+ const filepath = PathExt.join(directory, `${this.name}.chat`);
229
+ const content = JSON.stringify(this._serializeModel());
230
+ await this._contentsManager
231
+ .get(filepath, { content: false })
232
+ .catch(async () => {
233
+ await this._contentsManager
234
+ ?.get(directory, { content: false })
235
+ .catch(async () => {
236
+ const dir = await this._contentsManager.newUntitled({
237
+ type: 'directory'
238
+ });
239
+ await this._contentsManager.rename(dir.path, directory);
240
+ });
241
+ const file = await this._contentsManager.newUntitled({ ext: '.chat' });
242
+ await this._contentsManager?.rename(file.path, filepath);
243
+ });
244
+ await this._contentsManager.save(filepath, {
245
+ content,
246
+ type: 'file',
247
+ format: 'text'
248
+ });
249
+ };
250
+ /**
251
+ * Restore the chat from a json file.
252
+ *
253
+ * @param silent - Whether a log should be displayed in the console if the
254
+ * restoration is not possible.
255
+ */
256
+ restore = async (filepath, silent = false) => {
257
+ if (!this._contentsManager) {
258
+ return false;
259
+ }
260
+ const contentModel = await this._contentsManager
261
+ .get(filepath, { content: true })
262
+ .catch(() => {
263
+ if (!silent) {
264
+ console.log(`There is no backup for chat '${this.name}'`);
265
+ }
266
+ return;
267
+ });
268
+ if (!contentModel) {
269
+ return false;
270
+ }
271
+ const content = JSON.parse(contentModel.content);
272
+ if (content.metadata?.provider) {
273
+ if (this._settingsModel.getProvider(content.metadata.provider)) {
274
+ this._agentManager.activeProvider = content.metadata.provider;
275
+ }
276
+ else if (!silent) {
277
+ console.log(`Provider '${content.metadata.provider}' doesn't exist, it can't be restored.`);
278
+ }
279
+ }
280
+ else if (!silent) {
281
+ console.log(`Provider not providing when restoring ${filepath}.`);
282
+ }
283
+ const messages = content.messages.map(message => {
284
+ let attachments = [];
285
+ if (content.attachments && message.attachments) {
286
+ attachments =
287
+ message.attachments.map(index => content.attachments[index]) ?? [];
288
+ }
289
+ return {
290
+ ...message,
291
+ sender: content.users[message.sender] ?? { username: 'unknown' },
292
+ mentions: message.mentions?.map(mention => content.users[mention]),
293
+ attachments
294
+ };
295
+ });
296
+ this.clearMessages();
297
+ this.messagesInserted(0, messages);
298
+ this._agentManager.setHistory(messages);
299
+ this.autosave = content.metadata?.autosave ?? false;
300
+ return true;
301
+ };
302
+ /**
303
+ * Serialize the model for backup
304
+ */
305
+ _serializeModel() {
306
+ const provider = this._agentManager.activeProvider;
307
+ const messages = [];
308
+ const users = {};
309
+ const attachmentMap = new Map(); // JSON → index
310
+ const attachmentsList = []; // Actual attachments
311
+ this.messages.forEach(message => {
312
+ let attachmentIndexes = [];
313
+ if (message.attachments) {
314
+ attachmentIndexes = message.attachments.map(attachment => {
315
+ const attachmentJson = JSON.stringify(attachment);
316
+ let index;
317
+ if (attachmentMap.has(attachmentJson)) {
318
+ index = attachmentMap.get(attachmentJson);
319
+ }
320
+ else {
321
+ index = attachmentsList.length;
322
+ attachmentMap.set(attachmentJson, index);
323
+ attachmentsList.push(attachment);
324
+ }
325
+ return index.toString();
326
+ });
327
+ }
328
+ messages.push({
329
+ ...message.content,
330
+ sender: message.sender.username,
331
+ mentions: message.mentions?.map(user => user.username),
332
+ attachments: attachmentIndexes
333
+ });
334
+ if (!users[message.sender.username]) {
335
+ users[message.sender.username] = message.sender;
336
+ }
337
+ });
338
+ const attachments = Object.fromEntries(attachmentsList.map((item, index) => [index, item]));
339
+ return {
340
+ messages,
341
+ users,
342
+ attachments,
343
+ metadata: {
344
+ provider,
345
+ autosave: this.autosave
346
+ }
347
+ };
348
+ }
175
349
  /**
176
350
  * Gets the AI user information for system messages.
177
351
  */
@@ -352,13 +526,18 @@ export class AIChatModel extends AbstractChatModel {
352
526
  };
353
527
  this._toolContexts.set(event.data.callId, context);
354
528
  const toolCallMessage = {
355
- body: Private.buildToolCallHtml({
356
- toolName: context.toolName,
357
- input: context.input,
358
- status: context.status,
359
- summary: context.summary,
360
- trans: this._trans
361
- }),
529
+ body: '',
530
+ mime_model: {
531
+ data: {
532
+ 'application/vnd.jupyter.chat.components': 'tool-call'
533
+ },
534
+ metadata: {
535
+ toolName: context.toolName,
536
+ input: context.input,
537
+ status: context.status,
538
+ summary: context.summary
539
+ }
540
+ },
362
541
  sender: this._getAIUser(),
363
542
  id: messageId,
364
543
  time: Date.now() / 1000,
@@ -383,7 +562,8 @@ export class AIChatModel extends AbstractChatModel {
383
562
  });
384
563
  for (const bundle of mimeBundles) {
385
564
  this.messageAdded({
386
- body: bundle,
565
+ body: '',
566
+ mime_model: bundle,
387
567
  sender: this._getAIUser(),
388
568
  id: UUID.uuid4(),
389
569
  time: Date.now() / 1000,
@@ -456,15 +636,20 @@ export class AIChatModel extends AbstractChatModel {
456
636
  }
457
637
  context.status = status;
458
638
  existingMessage.update({
459
- body: Private.buildToolCallHtml({
460
- toolName: context.toolName,
461
- input: context.input,
462
- status: context.status,
463
- summary: context.summary,
464
- output,
465
- approvalId: context.approvalId,
466
- trans: this._trans
467
- })
639
+ mime_model: {
640
+ data: {
641
+ 'application/vnd.jupyter.chat.components': 'tool-call'
642
+ },
643
+ metadata: {
644
+ toolName: context.toolName,
645
+ input: context.input,
646
+ status: context.status,
647
+ summary: context.summary,
648
+ output,
649
+ targetId: this.name,
650
+ approvalId: context.approvalId
651
+ }
652
+ }
468
653
  });
469
654
  }
470
655
  /**
@@ -695,7 +880,10 @@ export class AIChatModel extends AbstractChatModel {
695
880
  _agentManager;
696
881
  _currentStreamingMessage = null;
697
882
  _nameChanged = new Signal(this);
698
- _trans;
883
+ _contentsManager;
884
+ _autosave = false;
885
+ _autosaveChanged = new Signal(this);
886
+ _autosaveDebouncer;
699
887
  }
700
888
  var Private;
701
889
  (function (Private) {
@@ -780,120 +968,4 @@ var Private;
780
968
  }
781
969
  }
782
970
  Private.formatToolOutput = formatToolOutput;
783
- function escapeHtml(value) {
784
- // Prefer the same native escaping approach used in JupyterLab itself
785
- // (e.g. `@jupyterlab/completer`).
786
- if (typeof document !== 'undefined') {
787
- const node = document.createElement('span');
788
- node.textContent = value;
789
- return node.innerHTML;
790
- }
791
- // Fallback
792
- return value
793
- .replace(/&/g, '&amp;')
794
- .replace(/</g, '&lt;')
795
- .replace(/>/g, '&gt;')
796
- .replace(/"/g, '&quot;')
797
- .replace(/'/g, '&#39;');
798
- }
799
- Private.escapeHtml = escapeHtml;
800
- const STATUS_CONFIG = {
801
- pending: {
802
- cssClass: 'jp-ai-tool-pending',
803
- statusClass: 'jp-ai-tool-status-pending'
804
- },
805
- awaiting_approval: {
806
- cssClass: 'jp-ai-tool-pending',
807
- statusClass: 'jp-ai-tool-status-approval',
808
- open: true
809
- },
810
- approved: {
811
- cssClass: 'jp-ai-tool-pending',
812
- statusClass: 'jp-ai-tool-status-completed'
813
- },
814
- rejected: {
815
- cssClass: 'jp-ai-tool-error',
816
- statusClass: 'jp-ai-tool-status-error'
817
- },
818
- completed: {
819
- cssClass: 'jp-ai-tool-completed',
820
- statusClass: 'jp-ai-tool-status-completed'
821
- },
822
- error: {
823
- cssClass: 'jp-ai-tool-error',
824
- statusClass: 'jp-ai-tool-status-error'
825
- }
826
- };
827
- /**
828
- * Returns the translated status text for a given tool status.
829
- */
830
- const getStatusText = (status, trans) => {
831
- switch (status) {
832
- case 'pending':
833
- return trans.__('Running...');
834
- case 'awaiting_approval':
835
- return trans.__('Awaiting Approval');
836
- case 'approved':
837
- return trans.__('Approved - Executing...');
838
- case 'rejected':
839
- return trans.__('Rejected');
840
- case 'completed':
841
- return trans.__('Completed');
842
- case 'error':
843
- return trans.__('Error');
844
- }
845
- };
846
- /**
847
- * Builds HTML for a tool call display.
848
- */
849
- function buildToolCallHtml(options) {
850
- const { toolName, input, status, summary, output, approvalId, trans } = options;
851
- const config = STATUS_CONFIG[status];
852
- const statusText = getStatusText(status, trans);
853
- const escapedToolName = escapeHtml(toolName);
854
- const escapedInput = escapeHtml(input);
855
- const openAttr = config.open ? ' open' : '';
856
- const summaryHtml = summary
857
- ? `<span class="jp-ai-tool-summary">${escapeHtml(summary)}</span>`
858
- : '';
859
- let bodyContent = `
860
- <div class="jp-ai-tool-section">
861
- <div class="jp-ai-tool-label">${trans.__('Input')}</div>
862
- <pre class="jp-ai-tool-code"><code>${escapedInput}</code></pre>
863
- </div>`;
864
- // Add approval buttons if awaiting approval
865
- if (status === 'awaiting_approval' && approvalId) {
866
- bodyContent += `
867
- <div class="jp-ai-tool-approval-buttons jp-ai-approval-id--${approvalId}">
868
- <button class="jp-ai-approval-btn jp-ai-approval-approve">${trans.__('Approve')}</button>
869
- <button class="jp-ai-approval-btn jp-ai-approval-reject">${trans.__('Reject')}</button>
870
- </div>`;
871
- }
872
- // Add output/result section if provided
873
- if (output !== undefined) {
874
- const escapedOutput = escapeHtml(output);
875
- const label = status === 'error' ? trans.__('Error') : trans.__('Result');
876
- bodyContent += `
877
- <div class="jp-ai-tool-section">
878
- <div class="jp-ai-tool-label">${label}</div>
879
- <pre class="jp-ai-tool-code"><code>${escapedOutput}</code></pre>
880
- </div>`;
881
- }
882
- const HTMLContent = `<details class="jp-ai-tool-call ${config.cssClass}"${openAttr}>
883
- <summary class="jp-ai-tool-header">
884
- <div class="jp-ai-tool-icon">⚡</div>
885
- <div class="jp-ai-tool-title">${escapedToolName}${summaryHtml}</div>
886
- <div class="jp-ai-tool-status ${config.statusClass}">${statusText}</div>
887
- </summary>
888
- <div class="jp-ai-tool-body">${bodyContent}
889
- </div>
890
- </details>`;
891
- return {
892
- trusted: true,
893
- data: {
894
- 'text/html': HTMLContent
895
- }
896
- };
897
- }
898
- Private.buildToolCallHtml = buildToolCallHtml;
899
971
  })(Private || (Private = {}));
@@ -1,7 +1,6 @@
1
1
  import { CompletionHandler, IInlineCompletionContext, IInlineCompletionList, IInlineCompletionProvider } from '@jupyterlab/completer';
2
2
  import { ISecretsManager } from 'jupyter-secrets-manager';
3
- import { AISettingsModel } from '../models/settings-model';
4
- import { type IProviderRegistry } from '../tokens';
3
+ import { type IAISettingsModel, type IProviderRegistry } from '../tokens';
5
4
  /**
6
5
  * Configuration interface for provider-specific completion behavior
7
6
  */
@@ -68,7 +67,7 @@ export declare namespace AICompletionProvider {
68
67
  /**
69
68
  * The AI settings model.
70
69
  */
71
- settingsModel: AISettingsModel;
70
+ settingsModel: IAISettingsModel;
72
71
  /**
73
72
  * The provider registry
74
73
  */
@@ -1,6 +1,6 @@
1
- import { AISettingsModel } from '../models/settings-model';
2
1
  import { ReactWidget } from '@jupyterlab/ui-components';
3
2
  import type { TranslationBundle } from '@jupyterlab/translation';
3
+ import type { IAISettingsModel } from '../tokens';
4
4
  /**
5
5
  * The completion status props.
6
6
  */
@@ -8,7 +8,7 @@ interface ICompletionStatusProps {
8
8
  /**
9
9
  * The settings model.
10
10
  */
11
- settingsModel: AISettingsModel;
11
+ settingsModel: IAISettingsModel;
12
12
  /**
13
13
  * The application language translator.
14
14
  */
@@ -1,6 +1,6 @@
1
1
  import { InputToolbarRegistry } from '@jupyter/chat';
2
2
  import type { TranslationBundle } from '@jupyterlab/translation';
3
- import { AISettingsModel } from '../models/settings-model';
3
+ import type { IAISettingsModel } from '../tokens';
4
4
  /**
5
5
  * Properties for the model select component.
6
6
  */
@@ -8,7 +8,7 @@ export interface IModelSelectProps extends InputToolbarRegistry.IToolbarItemProp
8
8
  /**
9
9
  * The settings model to get available models and current selection from.
10
10
  */
11
- settingsModel: AISettingsModel;
11
+ settingsModel: IAISettingsModel;
12
12
  /**
13
13
  * The application language translator.
14
14
  */
@@ -21,4 +21,4 @@ export declare function ModelSelect(props: IModelSelectProps): JSX.Element;
21
21
  /**
22
22
  * Factory function returning the toolbar item for model selection.
23
23
  */
24
- export declare function createModelSelectItem(settingsModel: AISettingsModel, translator: TranslationBundle): InputToolbarRegistry.IToolbarItem;
24
+ export declare function createModelSelectItem(settingsModel: IAISettingsModel, translator: TranslationBundle): InputToolbarRegistry.IToolbarItem;
@@ -0,0 +1,31 @@
1
+ import { ReactWidget } from '@jupyterlab/ui-components';
2
+ import type { TranslationBundle } from '@jupyterlab/translation';
3
+ import React from 'react';
4
+ import { AIChatModel } from '../chat-model';
5
+ /**
6
+ * Properties for the SaveButton component.
7
+ */
8
+ export interface ISaveButtonProps {
9
+ /**
10
+ * The chat model, used to listen for message changes for auto-save.
11
+ */
12
+ model: AIChatModel;
13
+ /**
14
+ * The application language translator.
15
+ */
16
+ translator: TranslationBundle;
17
+ }
18
+ /**
19
+ * A split button for saving the chat, with a button to toggle auto-save.
20
+ * When auto-save is active, the save button displays the JupyterLab
21
+ * toggled-on appearance (inset box-shadow all around).
22
+ */
23
+ export declare function SaveComponent(props: ISaveButtonProps): JSX.Element;
24
+ /**
25
+ * A Lumino widget wrapping the SaveButton React component.
26
+ */
27
+ export declare class SaveComponentWidget extends ReactWidget {
28
+ constructor(options: ISaveButtonProps);
29
+ protected render(): React.ReactElement;
30
+ private _options;
31
+ }