@jupyterlite/ai 0.17.0 → 0.18.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.
@@ -7,7 +7,7 @@ import {
7
7
  import type { TranslationBundle } from '@jupyterlab/translation';
8
8
  import React, { useEffect, useState } from 'react';
9
9
 
10
- import { AIChatModel } from '../chat-model';
10
+ import { IAIChatModel } from '../tokens';
11
11
 
12
12
  const COMPONENT_CLASS = 'jp-ai-SaveButton';
13
13
  const AUTOSAVE_BUTTON_CLASS = 'jp-ai-AutoSaveButton';
@@ -19,7 +19,7 @@ export interface ISaveButtonProps {
19
19
  /**
20
20
  * The chat model, used to listen for message changes for auto-save.
21
21
  */
22
- model: AIChatModel;
22
+ model: IAIChatModel;
23
23
  /**
24
24
  * The application language translator.
25
25
  */
@@ -40,7 +40,7 @@ export function SaveComponent(props: ISaveButtonProps): JSX.Element {
40
40
  * Effect that update the autosave state when it is updated on the model.
41
41
  */
42
42
  useEffect(() => {
43
- const updateAutosave = (_: AIChatModel, value: boolean) => {
43
+ const updateAutosave = (_: IAIChatModel, value: boolean) => {
44
44
  setAutosave(value);
45
45
  };
46
46
 
package/src/index.ts CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  ToolbarButton
59
59
  } from '@jupyterlab/ui-components';
60
60
 
61
- import { PromiseDelegate, UUID } from '@lumino/coreutils';
61
+ import { UUID } from '@lumino/coreutils';
62
62
 
63
63
  import { DisposableSet } from '@lumino/disposable';
64
64
 
@@ -70,8 +70,6 @@ import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
70
70
 
71
71
  import { AgentManagerFactory } from './agent';
72
72
 
73
- import { AIChatModel } from './chat-model';
74
-
75
73
  import { RenderedMessageOutputAreaCompat } from './rendered-message-outputarea';
76
74
 
77
75
  import { ClearCommandProvider } from './chat-commands/clear';
@@ -95,7 +93,8 @@ import {
95
93
  IProviderRegistry,
96
94
  IToolRegistry,
97
95
  ISkillRegistry,
98
- SECRETS_NAMESPACE
96
+ SECRETS_NAMESPACE,
97
+ IAIChatModel
99
98
  } from './tokens';
100
99
 
101
100
  import {
@@ -526,7 +525,7 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
526
525
 
527
526
  let usageWidget: UsageWidget | null = null;
528
527
  chatPanel.chatOpened.connect((_, widget) => {
529
- const model = widget.model as AIChatModel;
528
+ const model = widget.model as IAIChatModel;
530
529
 
531
530
  // Add the widget to the tracker.
532
531
  tracker.add(widget);
@@ -585,11 +584,9 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
585
584
  );
586
585
 
587
586
  if (aiWriting) {
588
- widget.inputToolbarRegistry?.hide('send');
589
587
  widget.inputToolbarRegistry?.show('stop');
590
588
  } else {
591
589
  widget.inputToolbarRegistry?.hide('stop');
592
- widget.inputToolbarRegistry?.show('send');
593
590
  }
594
591
  }
595
592
 
@@ -628,7 +625,7 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
628
625
  args: widget => ({
629
626
  name: widget.model.name,
630
627
  area: widget instanceof MainAreaChat ? 'main' : 'side',
631
- provider: (widget.model as AIChatModel).agentManager.activeProvider
628
+ provider: (widget.model as IAIChatModel).agentManager.activeProvider
632
629
  }),
633
630
  name: widget => {
634
631
  const area = widget instanceof MainAreaChat ? 'main' : 'side';
@@ -662,24 +659,39 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
662
659
  );
663
660
 
664
661
  /**
665
- * The callback to approve or reject a tool.
662
+ * The callback for grouped tool calls permission decisions.
666
663
  */
667
- function toolCallApproval(
668
- targetId: string,
669
- approvalId: string,
670
- isApproved: boolean
664
+ function toolCallPermissionDecision(
665
+ sessionId: string,
666
+ toolCallId: string,
667
+ optionId: string
671
668
  ) {
672
- const model = tracker.find(chat => chat.model.name === targetId)?.model;
669
+ const model = tracker.find(chat => chat.model.name === sessionId)
670
+ ?.model as IAIChatModel;
673
671
  if (!model) {
674
672
  return;
675
673
  }
674
+
675
+ const isApproved = optionId === 'approve';
676
676
  isApproved
677
- ? (model as AIChatModel).agentManager.approveToolCall(approvalId)
678
- : (model as AIChatModel).agentManager.rejectToolCall(approvalId);
677
+ ? model.agentManager.approveToolCall(toolCallId)
678
+ : model.agentManager.rejectToolCall(toolCallId);
679
679
  }
680
680
 
681
681
  if (chatComponentsFactory) {
682
- chatComponentsFactory.toolCallApproval = toolCallApproval;
682
+ chatComponentsFactory.toolCallPermissionDecision =
683
+ toolCallPermissionDecision;
684
+
685
+ chatComponentsFactory.removeQueuedMessage = (
686
+ targetId: string,
687
+ messageId: string
688
+ ) => {
689
+ const model = tracker.find(chat => chat.model.name === targetId)?.model;
690
+ if (!model) {
691
+ return;
692
+ }
693
+ (model as IAIChatModel).removeQueuedMessage(messageId);
694
+ };
683
695
  }
684
696
 
685
697
  return tracker;
@@ -755,12 +767,13 @@ function registerCommands(
755
767
  }
756
768
  });
757
769
 
758
- const openInMain = (model: AIChatModel): MainAreaChat => {
770
+ const openInMain = (model: IAIChatModel): MainAreaChat => {
771
+ const inputToolbarRegistry = inputToolbarFactory.create();
759
772
  const content = new ChatWidget({
760
773
  model,
761
774
  rmRegistry,
762
775
  themeManager: themeManager ?? null,
763
- inputToolbarRegistry: inputToolbarFactory.create(),
776
+ inputToolbarRegistry,
764
777
  attachmentOpenerRegistry,
765
778
  chatCommandRegistry
766
779
  });
@@ -827,7 +840,7 @@ function registerCommands(
827
840
  return;
828
841
  }
829
842
  return tracker.find(widget => {
830
- const model = widget.model as AIChatModel;
843
+ const model = widget.model as IAIChatModel;
831
844
  return (
832
845
  (!name || widget.model.name === name) &&
833
846
  (!provider || model.agentManager.activeProvider === provider)
@@ -1064,11 +1077,11 @@ function registerCommands(
1064
1077
  return false;
1065
1078
  }
1066
1079
  let previousWidget: ChatWidget | MainAreaChat | undefined;
1067
- let previousModel: AIChatModel | undefined;
1080
+ let previousModel: IAIChatModel | undefined;
1068
1081
  tracker.forEach(widget => {
1069
1082
  if (widget.model.name === args.name) {
1070
1083
  previousWidget = widget;
1071
- previousModel = widget.model as AIChatModel;
1084
+ previousModel = widget.model as IAIChatModel;
1072
1085
  }
1073
1086
  });
1074
1087
 
@@ -1079,61 +1092,26 @@ function registerCommands(
1079
1092
  return false;
1080
1093
  }
1081
1094
 
1082
- // Listen for the widget updated in tracker, to ensure the previous model name
1083
- // has been updated. This is required to remove the widget from the restorer
1084
- // when the previous widget is disposed.
1085
- const trackerUpdated = new PromiseDelegate<boolean>();
1086
- const widgetUpdated = (_: any, widget: ChatWidget | MainAreaChat) => {
1087
- if (widget.model === previousModel) {
1088
- trackerUpdated.resolve(true);
1089
- }
1090
- };
1091
- tracker.widgetUpdated.connect(widgetUpdated);
1092
-
1093
- // Rename temporary the previous model to be able to reuse this name for the new
1094
- // model. The previous is intended to be disposed anyway.
1095
- previousModel.name = UUID.uuid4();
1096
-
1097
- // Create a new model by duplicating the previous model attributes.
1098
- const model = modelRegistry.createModel({
1099
- name: args.name as string,
1100
- activeProvider: previousModel.agentManager.activeProvider,
1101
- tokenUsage: previousModel.agentManager.tokenUsage,
1102
- messages: previousModel.messages,
1103
- autosave: previousModel.autosave,
1104
- title: previousModel.title
1105
- });
1106
-
1107
- // Wait (with timeout) for the tracker to have updated the previous widget.
1108
- const status = await Promise.any([
1109
- trackerUpdated.promise,
1110
- new Promise<boolean>(r =>
1111
- setTimeout(() => {
1112
- r(false);
1113
- }, 2000)
1114
- )
1115
- ]);
1116
- tracker.widgetUpdated.disconnect(widgetUpdated);
1117
-
1118
- if (!status) {
1119
- return false;
1120
- }
1121
-
1122
1095
  if (area === 'main') {
1123
- openInMain(model);
1096
+ // Temporarily bypass model disposal to transport model to main view
1097
+ // to keep the conversation when switching views
1098
+ // TODO: Remove this code when jupyter-chat PR #423 is merged and released
1099
+ const originalDispose = previousModel.dispose.bind(previousModel);
1100
+ previousModel.dispose = () => {};
1124
1101
 
1125
1102
  if (previousWidget instanceof ChatWidget) {
1126
- // Clean up the side-panel model entry before disposing the previous
1127
- // widget/model state.
1128
1103
  if (!disposeSideChatModel(previousModel)) {
1129
1104
  previousWidget.dispose();
1130
- previousModel.dispose();
1131
1105
  }
1132
1106
  }
1107
+
1108
+ // Restore model disposal and transport to main view
1109
+ previousModel.dispose = originalDispose;
1110
+ openInMain(previousModel);
1133
1111
  } else {
1112
+ // MainAreaChat disposal does not dispose the model internally, so this is safe.
1134
1113
  previousWidget?.dispose();
1135
- previousModel.dispose();
1136
- chatPanel.open({ model });
1114
+ chatPanel.open({ model: previousModel });
1137
1115
  }
1138
1116
 
1139
1117
  return true;
@@ -1162,15 +1140,15 @@ function registerCommands(
1162
1140
  caption: trans.__('Save the chat as local file'),
1163
1141
  icon: saveIcon,
1164
1142
  execute: async (args): Promise<boolean> => {
1165
- let model: AIChatModel | null = null;
1143
+ let model: IAIChatModel | null = null;
1166
1144
  if (args.name) {
1167
1145
  tracker.forEach(widget => {
1168
1146
  if (widget.model.name === args.name) {
1169
- model = widget.model as AIChatModel;
1147
+ model = widget.model as IAIChatModel;
1170
1148
  }
1171
1149
  });
1172
1150
  } else {
1173
- model = (tracker.currentWidget?.model as AIChatModel) ?? null;
1151
+ model = (tracker.currentWidget?.model as IAIChatModel) ?? null;
1174
1152
  }
1175
1153
  if (model === null) {
1176
1154
  console.log('No chat to save');
@@ -1207,15 +1185,15 @@ function registerCommands(
1207
1185
  console.warn('The restoration is not possible');
1208
1186
  return false;
1209
1187
  }
1210
- let model: AIChatModel | null = null;
1188
+ let model: IAIChatModel | null = null;
1211
1189
  if (args.name) {
1212
1190
  tracker.forEach(widget => {
1213
1191
  if (widget.model.name === args.name) {
1214
- model = widget.model as AIChatModel;
1192
+ model = widget.model as IAIChatModel;
1215
1193
  }
1216
1194
  });
1217
1195
  } else {
1218
- model = (tracker.currentWidget?.model as AIChatModel) ?? null;
1196
+ model = (tracker.currentWidget?.model as IAIChatModel) ?? null;
1219
1197
  }
1220
1198
  if (model === null) {
1221
1199
  console.warn('There is no chat to restore');
@@ -29,6 +29,7 @@ export class AISettingsModel extends VDomModel implements IAISettingsModel {
29
29
  diffDisplayMode: 'split',
30
30
  skillsPaths: ['.agents/skills', '_agents/skills'],
31
31
  chatBackupDirectory: '',
32
+ autoTitle: false,
32
33
  commandsRequiringApproval: [
33
34
  'notebook:restart-run-all',
34
35
  'notebook:run-cell',
@@ -4,7 +4,7 @@ import { createMistral } from '@ai-sdk/mistral';
4
4
  import { createOpenAI } from '@ai-sdk/openai';
5
5
  import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
6
6
 
7
- import { BUILT_IN_PROVIDER_MODEL_INFO } from './generated-context-windows';
7
+ import { BUILT_IN_PROVIDER_MODEL_INFO } from './generated-model-info';
8
8
  import type { IProviderInfo } from '../tokens';
9
9
  import type { IModelOptions } from './models';
10
10