@jupyterlite/ai 0.15.0 → 0.17.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 +12 -2
- package/lib/agent.js +112 -17
- package/lib/chat-commands/clear.js +1 -1
- package/lib/chat-model-handler.js +4 -1
- package/lib/chat-model.d.ts +25 -24
- package/lib/chat-model.js +262 -132
- package/lib/components/clear-button.d.ts +1 -1
- package/lib/components/clear-button.js +1 -1
- package/lib/components/index.d.ts +1 -1
- package/lib/components/index.js +1 -1
- package/lib/components/{token-usage-display.d.ts → usage-display.d.ts} +11 -11
- package/lib/components/usage-display.js +109 -0
- package/lib/index.js +205 -20
- package/lib/models/settings-model.js +1 -0
- package/lib/providers/built-in-providers.js +5 -0
- package/lib/providers/generated-context-windows.d.ts +8 -0
- package/lib/providers/generated-context-windows.js +96 -0
- package/lib/providers/model-info.d.ts +3 -0
- package/lib/providers/model-info.js +58 -0
- package/lib/tokens.d.ts +34 -3
- package/lib/tokens.js +8 -7
- package/lib/widgets/ai-settings.js +9 -0
- package/lib/widgets/main-area-chat.d.ts +1 -0
- package/lib/widgets/main-area-chat.js +10 -4
- package/lib/widgets/provider-config-dialog.js +18 -5
- package/package.json +3 -2
- package/schema/settings-model.json +11 -0
- package/src/agent.ts +151 -21
- package/src/chat-commands/clear.ts +1 -1
- package/src/chat-model-handler.ts +6 -1
- package/src/chat-model.ts +350 -175
- package/src/components/clear-button.tsx +3 -3
- package/src/components/index.ts +1 -1
- package/src/components/usage-display.tsx +208 -0
- package/src/index.ts +250 -26
- package/src/models/settings-model.ts +1 -0
- package/src/providers/built-in-providers.ts +5 -0
- package/src/providers/generated-context-windows.ts +102 -0
- package/src/providers/model-info.ts +88 -0
- package/src/tokens.ts +46 -10
- package/src/widgets/ai-settings.tsx +42 -0
- package/src/widgets/main-area-chat.ts +12 -4
- package/src/widgets/provider-config-dialog.tsx +45 -5
- package/lib/components/token-usage-display.js +0 -72
- package/src/components/token-usage-display.tsx +0 -137
package/lib/chat-model.js
CHANGED
|
@@ -49,6 +49,31 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
49
49
|
}
|
|
50
50
|
this.setReady();
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* A signal emitting when the chat name has changed.
|
|
54
|
+
*/
|
|
55
|
+
get nameChanged() {
|
|
56
|
+
return this._nameChanged;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The title of the chat.
|
|
60
|
+
*/
|
|
61
|
+
get title() {
|
|
62
|
+
return this._title;
|
|
63
|
+
}
|
|
64
|
+
set title(value) {
|
|
65
|
+
this._title = value;
|
|
66
|
+
if (this.autosave) {
|
|
67
|
+
this._autosaveDebouncer.invoke();
|
|
68
|
+
}
|
|
69
|
+
this._titleChanged.emit(this._title);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A signal emitting when the chat title has changed.
|
|
73
|
+
*/
|
|
74
|
+
get titleChanged() {
|
|
75
|
+
return this._titleChanged;
|
|
76
|
+
}
|
|
52
77
|
/**
|
|
53
78
|
* Whether to save the chat automatically.
|
|
54
79
|
*/
|
|
@@ -74,12 +99,6 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
74
99
|
get autosaveChanged() {
|
|
75
100
|
return this._autosaveChanged;
|
|
76
101
|
}
|
|
77
|
-
/**
|
|
78
|
-
* A signal emitting when the chat name has changed.
|
|
79
|
-
*/
|
|
80
|
-
get nameChanged() {
|
|
81
|
-
return this._nameChanged;
|
|
82
|
-
}
|
|
83
102
|
/**
|
|
84
103
|
* Gets the current user information.
|
|
85
104
|
*/
|
|
@@ -135,10 +154,10 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
135
154
|
/**
|
|
136
155
|
* Clears all messages from the chat and resets conversation state.
|
|
137
156
|
*/
|
|
138
|
-
clearMessages = () => {
|
|
157
|
+
clearMessages = async () => {
|
|
139
158
|
this.messagesDeleted(0, this.messages.length);
|
|
140
159
|
this._toolContexts.clear();
|
|
141
|
-
this._agentManager.clearHistory();
|
|
160
|
+
await this._agentManager.clearHistory();
|
|
142
161
|
};
|
|
143
162
|
/**
|
|
144
163
|
* Adds a non-user message to the chat (used by chat commands).
|
|
@@ -192,11 +211,18 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
192
211
|
// Process attachments and add their content to the message
|
|
193
212
|
let enhancedMessage = message.body;
|
|
194
213
|
if (this.input.attachments.length > 0) {
|
|
195
|
-
const
|
|
214
|
+
const { textContents, binaryParts } = await Private.processAttachments(this.input.attachments, this.input.documentManager);
|
|
196
215
|
this.input.clearAttachments();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
216
|
+
let textPart = message.body;
|
|
217
|
+
if (textContents.length > 0) {
|
|
218
|
+
textPart +=
|
|
219
|
+
'\n\n--- Attached Files ---\n' + textContents.join('\n\n');
|
|
220
|
+
}
|
|
221
|
+
if (binaryParts.length > 0) {
|
|
222
|
+
enhancedMessage = [{ type: 'text', text: textPart }, ...binaryParts];
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
enhancedMessage = textPart;
|
|
200
226
|
}
|
|
201
227
|
}
|
|
202
228
|
this.updateWriters([{ user: this._getAIUser() }]);
|
|
@@ -258,7 +284,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
258
284
|
return false;
|
|
259
285
|
}
|
|
260
286
|
const contentModel = await this._contentsManager
|
|
261
|
-
.get(filepath, { content: true })
|
|
287
|
+
.get(filepath, { content: true, type: 'file', format: 'text' })
|
|
262
288
|
.catch(() => {
|
|
263
289
|
if (!silent) {
|
|
264
290
|
console.log(`There is no backup for chat '${this.name}'`);
|
|
@@ -268,7 +294,13 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
268
294
|
if (!contentModel) {
|
|
269
295
|
return false;
|
|
270
296
|
}
|
|
271
|
-
|
|
297
|
+
let content;
|
|
298
|
+
try {
|
|
299
|
+
content = JSON.parse(contentModel.content);
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
throw `Error when parsing the chat ${filepath}\n${e}`;
|
|
303
|
+
}
|
|
272
304
|
if (content.metadata?.provider) {
|
|
273
305
|
if (this._settingsModel.getProvider(content.metadata.provider)) {
|
|
274
306
|
this._agentManager.activeProvider = content.metadata.provider;
|
|
@@ -293,12 +325,33 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
293
325
|
attachments
|
|
294
326
|
};
|
|
295
327
|
});
|
|
296
|
-
this.clearMessages();
|
|
328
|
+
await this.clearMessages();
|
|
297
329
|
this.messagesInserted(0, messages);
|
|
298
330
|
this._agentManager.setHistory(messages);
|
|
299
331
|
this.autosave = content.metadata?.autosave ?? false;
|
|
332
|
+
this.title = content.metadata?.title ?? null;
|
|
300
333
|
return true;
|
|
301
334
|
};
|
|
335
|
+
/**
|
|
336
|
+
* Request a title to this chat, regarding the message history.
|
|
337
|
+
*/
|
|
338
|
+
async requestTitle() {
|
|
339
|
+
const history = this.messages
|
|
340
|
+
.filter(msg => msg.body !== '')
|
|
341
|
+
.map(msg => `${msg.sender.username === 'ai-assistant' ? 'assistant' : 'user'}: ${msg.body}`)
|
|
342
|
+
.join('\n');
|
|
343
|
+
const messages = [
|
|
344
|
+
{
|
|
345
|
+
role: 'system',
|
|
346
|
+
content: "Generate a concise title (no more than 10 words) for the following conversation. Do not use formatting. Focus on the user's main intent."
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
role: 'user',
|
|
350
|
+
content: history
|
|
351
|
+
}
|
|
352
|
+
];
|
|
353
|
+
return this.agentManager.textResponse(messages);
|
|
354
|
+
}
|
|
302
355
|
/**
|
|
303
356
|
* Serialize the model for backup
|
|
304
357
|
*/
|
|
@@ -342,7 +395,8 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
342
395
|
attachments,
|
|
343
396
|
metadata: {
|
|
344
397
|
provider,
|
|
345
|
-
autosave: this.autosave
|
|
398
|
+
autosave: this.autosave,
|
|
399
|
+
...(this.title ? { title: this.title } : {})
|
|
346
400
|
}
|
|
347
401
|
};
|
|
348
402
|
}
|
|
@@ -652,49 +706,216 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
652
706
|
}
|
|
653
707
|
});
|
|
654
708
|
}
|
|
709
|
+
// Private fields
|
|
710
|
+
_settingsModel;
|
|
711
|
+
_user;
|
|
712
|
+
_toolContexts = new Map();
|
|
713
|
+
_agentManager;
|
|
714
|
+
_currentStreamingMessage = null;
|
|
715
|
+
_nameChanged = new Signal(this);
|
|
716
|
+
_contentsManager;
|
|
717
|
+
_autosave = false;
|
|
718
|
+
_autosaveChanged = new Signal(this);
|
|
719
|
+
_autosaveDebouncer;
|
|
720
|
+
_title = null;
|
|
721
|
+
_titleChanged = new Signal(this);
|
|
722
|
+
}
|
|
723
|
+
var Private;
|
|
724
|
+
(function (Private) {
|
|
725
|
+
const isPlainObject = (value) => {
|
|
726
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
727
|
+
};
|
|
728
|
+
const isDisplayOutput = (value) => {
|
|
729
|
+
if (!isPlainObject(value)) {
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
const output = value;
|
|
733
|
+
return (nbformat.isDisplayData(output) ||
|
|
734
|
+
nbformat.isDisplayUpdate(output) ||
|
|
735
|
+
nbformat.isExecuteResult(output));
|
|
736
|
+
};
|
|
737
|
+
const toMimeBundle = (value, trustedMimeTypes) => {
|
|
738
|
+
const data = value.data;
|
|
739
|
+
if (!isPlainObject(data) || Object.keys(data).length === 0) {
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
data: data,
|
|
744
|
+
...(isPlainObject(value.metadata)
|
|
745
|
+
? { metadata: value.metadata }
|
|
746
|
+
: {}),
|
|
747
|
+
// MIME auto-rendering only runs for explicitly configured command IDs.
|
|
748
|
+
// Trust handling is configurable to keep risky MIME execution opt-in.
|
|
749
|
+
...(Object.keys(data).some(m => trustedMimeTypes.has(m))
|
|
750
|
+
? { trusted: true }
|
|
751
|
+
: {})
|
|
752
|
+
};
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* Normalize arbitrary tool payloads into canonical display outputs.
|
|
756
|
+
*
|
|
757
|
+
* Tool outputs are not guaranteed to be raw Jupyter IOPub messages; they are
|
|
758
|
+
* often wrapped objects (for example `{ success, result: { outputs: [...] } }`).
|
|
759
|
+
*/
|
|
760
|
+
const toDisplayOutputs = (value) => {
|
|
761
|
+
if (isDisplayOutput(value)) {
|
|
762
|
+
return [value];
|
|
763
|
+
}
|
|
764
|
+
if (Array.isArray(value)) {
|
|
765
|
+
return value.filter(isDisplayOutput);
|
|
766
|
+
}
|
|
767
|
+
if (!isPlainObject(value)) {
|
|
768
|
+
return [];
|
|
769
|
+
}
|
|
770
|
+
if (Array.isArray(value.outputs)) {
|
|
771
|
+
return value.outputs.filter(isDisplayOutput);
|
|
772
|
+
}
|
|
773
|
+
if ('result' in value) {
|
|
774
|
+
return toDisplayOutputs(value.result);
|
|
775
|
+
}
|
|
776
|
+
return [];
|
|
777
|
+
};
|
|
778
|
+
/**
|
|
779
|
+
* Extract rendermime-ready mime bundles from arbitrary tool results.
|
|
780
|
+
*/
|
|
781
|
+
function extractMimeBundlesFromUnknown(content, options = {}) {
|
|
782
|
+
const bundles = [];
|
|
783
|
+
const outputs = toDisplayOutputs(content);
|
|
784
|
+
const trustedMimeTypes = new Set(options.trustedMimeTypes ?? []);
|
|
785
|
+
for (const output of outputs) {
|
|
786
|
+
const bundle = toMimeBundle(output, trustedMimeTypes);
|
|
787
|
+
if (bundle) {
|
|
788
|
+
bundles.push(bundle);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return bundles;
|
|
792
|
+
}
|
|
793
|
+
Private.extractMimeBundlesFromUnknown = extractMimeBundlesFromUnknown;
|
|
794
|
+
function formatToolOutput(outputData) {
|
|
795
|
+
if (typeof outputData === 'string') {
|
|
796
|
+
return outputData;
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
return JSON.stringify(outputData, null, 2);
|
|
800
|
+
}
|
|
801
|
+
catch {
|
|
802
|
+
return '[Complex object - cannot serialize]';
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
Private.formatToolOutput = formatToolOutput;
|
|
655
806
|
/**
|
|
656
|
-
* Processes file attachments and returns
|
|
807
|
+
* Processes file attachments and returns text contents and binary parts separately.
|
|
657
808
|
* @param attachments Array of file attachments to process
|
|
658
|
-
* @
|
|
809
|
+
* @param documentManager Optional document manager for file operations
|
|
810
|
+
* @returns Text contents and binary parts
|
|
659
811
|
*/
|
|
660
|
-
async
|
|
661
|
-
const
|
|
812
|
+
async function processAttachments(attachments, documentManager) {
|
|
813
|
+
const textContents = [];
|
|
814
|
+
const binaryParts = [];
|
|
815
|
+
if (!documentManager) {
|
|
816
|
+
return { textContents, binaryParts };
|
|
817
|
+
}
|
|
662
818
|
for (const attachment of attachments) {
|
|
663
819
|
try {
|
|
664
820
|
if (attachment.type === 'notebook' && attachment.cells?.length) {
|
|
665
|
-
const cellContents = await
|
|
821
|
+
const cellContents = await readNotebookCells(attachment, documentManager);
|
|
666
822
|
if (cellContents) {
|
|
667
|
-
|
|
823
|
+
textContents.push(cellContents);
|
|
668
824
|
}
|
|
669
825
|
}
|
|
670
826
|
else {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
827
|
+
let mimetype = attachment.mimetype;
|
|
828
|
+
const fileExtension = PathExt.extname(attachment.value).toLowerCase();
|
|
829
|
+
// Fetch mimetype from server metadata if not provided
|
|
830
|
+
if (!mimetype) {
|
|
831
|
+
try {
|
|
832
|
+
const diskModel = await documentManager.services.contents.get(attachment.value, { content: false });
|
|
833
|
+
mimetype = diskModel?.mimetype;
|
|
834
|
+
}
|
|
835
|
+
catch (e) {
|
|
836
|
+
console.warn(`Failed to fetch metadata for ${attachment.value}:`, e);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (mimetype?.startsWith('image/')) {
|
|
840
|
+
const data = await readBinaryAttachment(attachment, documentManager);
|
|
841
|
+
if (data) {
|
|
842
|
+
binaryParts.push({
|
|
843
|
+
type: 'image',
|
|
844
|
+
image: data,
|
|
845
|
+
mediaType: mimetype
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
else if (mimetype === 'application/pdf') {
|
|
850
|
+
const data = await readBinaryAttachment(attachment, documentManager);
|
|
851
|
+
if (data) {
|
|
852
|
+
binaryParts.push({
|
|
853
|
+
type: 'file',
|
|
854
|
+
data,
|
|
855
|
+
mediaType: mimetype,
|
|
856
|
+
filename: PathExt.basename(attachment.value)
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
const fileContent = await readFileAttachment(attachment, documentManager);
|
|
862
|
+
if (fileContent) {
|
|
863
|
+
const language = fileExtension === '.ipynb' ||
|
|
864
|
+
mimetype === 'application/x-ipynb+json'
|
|
865
|
+
? 'json'
|
|
866
|
+
: '';
|
|
867
|
+
textContents.push(`**File: ${attachment.value}**\n\`\`\`${language}\n${fileContent}\n\`\`\``);
|
|
868
|
+
}
|
|
676
869
|
}
|
|
677
870
|
}
|
|
678
871
|
}
|
|
679
872
|
catch (error) {
|
|
680
873
|
console.warn(`Failed to read attachment ${attachment.value}:`, error);
|
|
681
|
-
|
|
874
|
+
textContents.push(`**File: ${attachment.value}** (Could not read file)`);
|
|
682
875
|
}
|
|
683
876
|
}
|
|
684
|
-
return
|
|
877
|
+
return { textContents, binaryParts };
|
|
685
878
|
}
|
|
879
|
+
Private.processAttachments = processAttachments;
|
|
880
|
+
/**
|
|
881
|
+
* Reads a binary attachment and returns its base64-encoded content.
|
|
882
|
+
* @param attachment The attachment to read
|
|
883
|
+
* @param documentManager Optional document manager for file operations
|
|
884
|
+
* @returns Base64 string or null if unable to read
|
|
885
|
+
*/
|
|
886
|
+
async function readBinaryAttachment(attachment, documentManager) {
|
|
887
|
+
if (!documentManager) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
const diskModel = await documentManager.services.contents.get(attachment.value, { content: true });
|
|
892
|
+
if (diskModel?.content && diskModel.format === 'base64') {
|
|
893
|
+
// Strip whitespace/newlines
|
|
894
|
+
return diskModel.content.replace(/\s/g, '');
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
catch (error) {
|
|
899
|
+
console.warn(`Failed to read binary attachment ${attachment.value}:`, error);
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
Private.readBinaryAttachment = readBinaryAttachment;
|
|
686
904
|
/**
|
|
687
905
|
* Reads the content of a notebook cell.
|
|
688
906
|
* @param attachment The notebook attachment to read
|
|
907
|
+
* @param documentManager Optional document manager for file operations
|
|
689
908
|
* @returns Cell content as string or null if unable to read
|
|
690
909
|
*/
|
|
691
|
-
async
|
|
692
|
-
if (attachment.type !== 'notebook' ||
|
|
910
|
+
async function readNotebookCells(attachment, documentManager) {
|
|
911
|
+
if (attachment.type !== 'notebook' ||
|
|
912
|
+
!attachment.cells ||
|
|
913
|
+
!documentManager) {
|
|
693
914
|
return null;
|
|
694
915
|
}
|
|
695
916
|
try {
|
|
696
917
|
// Try reading from live notebook if open
|
|
697
|
-
const widget =
|
|
918
|
+
const widget = documentManager.findWidget(attachment.value);
|
|
698
919
|
let cellData;
|
|
699
920
|
let kernelLang = 'text';
|
|
700
921
|
const ymodel = widget?.context.model.sharedModel;
|
|
@@ -708,7 +929,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
708
929
|
}
|
|
709
930
|
else {
|
|
710
931
|
// Fallback: reading from disk
|
|
711
|
-
const model = await
|
|
932
|
+
const model = await documentManager.services.contents.get(attachment.value);
|
|
712
933
|
if (!model || model.type !== 'notebook') {
|
|
713
934
|
return null;
|
|
714
935
|
}
|
|
@@ -823,19 +1044,22 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
823
1044
|
return null;
|
|
824
1045
|
}
|
|
825
1046
|
}
|
|
1047
|
+
Private.readNotebookCells = readNotebookCells;
|
|
826
1048
|
/**
|
|
827
1049
|
* Reads the content of a file attachment.
|
|
828
1050
|
* @param attachment The file attachment to read
|
|
1051
|
+
* @param documentManager Optional document manager for file operations
|
|
829
1052
|
* @returns File content as string or null if unable to read
|
|
830
1053
|
*/
|
|
831
|
-
async
|
|
1054
|
+
async function readFileAttachment(attachment, documentManager) {
|
|
832
1055
|
// Handle both 'file' and 'notebook' types since both have a 'value' path
|
|
833
|
-
if (attachment.type !== 'file' && attachment.type !== 'notebook')
|
|
1056
|
+
if ((attachment.type !== 'file' && attachment.type !== 'notebook') ||
|
|
1057
|
+
!documentManager) {
|
|
834
1058
|
return null;
|
|
835
1059
|
}
|
|
836
1060
|
try {
|
|
837
1061
|
// Try reading from an open widget first
|
|
838
|
-
const widget =
|
|
1062
|
+
const widget = documentManager.findWidget(attachment.value);
|
|
839
1063
|
if (widget && widget.context && widget.context.model) {
|
|
840
1064
|
const model = widget.context.model;
|
|
841
1065
|
const ymodel = model.sharedModel;
|
|
@@ -847,7 +1071,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
847
1071
|
}
|
|
848
1072
|
}
|
|
849
1073
|
// If not open, load from disk
|
|
850
|
-
const diskModel = await
|
|
1074
|
+
const diskModel = await documentManager.services.contents.get(attachment.value);
|
|
851
1075
|
if (!diskModel?.content) {
|
|
852
1076
|
return null;
|
|
853
1077
|
}
|
|
@@ -873,99 +1097,5 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
873
1097
|
return null;
|
|
874
1098
|
}
|
|
875
1099
|
}
|
|
876
|
-
|
|
877
|
-
_settingsModel;
|
|
878
|
-
_user;
|
|
879
|
-
_toolContexts = new Map();
|
|
880
|
-
_agentManager;
|
|
881
|
-
_currentStreamingMessage = null;
|
|
882
|
-
_nameChanged = new Signal(this);
|
|
883
|
-
_contentsManager;
|
|
884
|
-
_autosave = false;
|
|
885
|
-
_autosaveChanged = new Signal(this);
|
|
886
|
-
_autosaveDebouncer;
|
|
887
|
-
}
|
|
888
|
-
var Private;
|
|
889
|
-
(function (Private) {
|
|
890
|
-
const isPlainObject = (value) => {
|
|
891
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
892
|
-
};
|
|
893
|
-
const isDisplayOutput = (value) => {
|
|
894
|
-
if (!isPlainObject(value)) {
|
|
895
|
-
return false;
|
|
896
|
-
}
|
|
897
|
-
const output = value;
|
|
898
|
-
return (nbformat.isDisplayData(output) ||
|
|
899
|
-
nbformat.isDisplayUpdate(output) ||
|
|
900
|
-
nbformat.isExecuteResult(output));
|
|
901
|
-
};
|
|
902
|
-
const toMimeBundle = (value, trustedMimeTypes) => {
|
|
903
|
-
const data = value.data;
|
|
904
|
-
if (!isPlainObject(data) || Object.keys(data).length === 0) {
|
|
905
|
-
return null;
|
|
906
|
-
}
|
|
907
|
-
return {
|
|
908
|
-
data: data,
|
|
909
|
-
...(isPlainObject(value.metadata)
|
|
910
|
-
? { metadata: value.metadata }
|
|
911
|
-
: {}),
|
|
912
|
-
// MIME auto-rendering only runs for explicitly configured command IDs.
|
|
913
|
-
// Trust handling is configurable to keep risky MIME execution opt-in.
|
|
914
|
-
...(Object.keys(data).some(m => trustedMimeTypes.has(m))
|
|
915
|
-
? { trusted: true }
|
|
916
|
-
: {})
|
|
917
|
-
};
|
|
918
|
-
};
|
|
919
|
-
/**
|
|
920
|
-
* Normalize arbitrary tool payloads into canonical display outputs.
|
|
921
|
-
*
|
|
922
|
-
* Tool outputs are not guaranteed to be raw Jupyter IOPub messages; they are
|
|
923
|
-
* often wrapped objects (for example `{ success, result: { outputs: [...] } }`).
|
|
924
|
-
*/
|
|
925
|
-
const toDisplayOutputs = (value) => {
|
|
926
|
-
if (isDisplayOutput(value)) {
|
|
927
|
-
return [value];
|
|
928
|
-
}
|
|
929
|
-
if (Array.isArray(value)) {
|
|
930
|
-
return value.filter(isDisplayOutput);
|
|
931
|
-
}
|
|
932
|
-
if (!isPlainObject(value)) {
|
|
933
|
-
return [];
|
|
934
|
-
}
|
|
935
|
-
if (Array.isArray(value.outputs)) {
|
|
936
|
-
return value.outputs.filter(isDisplayOutput);
|
|
937
|
-
}
|
|
938
|
-
if ('result' in value) {
|
|
939
|
-
return toDisplayOutputs(value.result);
|
|
940
|
-
}
|
|
941
|
-
return [];
|
|
942
|
-
};
|
|
943
|
-
/**
|
|
944
|
-
* Extract rendermime-ready mime bundles from arbitrary tool results.
|
|
945
|
-
*/
|
|
946
|
-
function extractMimeBundlesFromUnknown(content, options = {}) {
|
|
947
|
-
const bundles = [];
|
|
948
|
-
const outputs = toDisplayOutputs(content);
|
|
949
|
-
const trustedMimeTypes = new Set(options.trustedMimeTypes ?? []);
|
|
950
|
-
for (const output of outputs) {
|
|
951
|
-
const bundle = toMimeBundle(output, trustedMimeTypes);
|
|
952
|
-
if (bundle) {
|
|
953
|
-
bundles.push(bundle);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
return bundles;
|
|
957
|
-
}
|
|
958
|
-
Private.extractMimeBundlesFromUnknown = extractMimeBundlesFromUnknown;
|
|
959
|
-
function formatToolOutput(outputData) {
|
|
960
|
-
if (typeof outputData === 'string') {
|
|
961
|
-
return outputData;
|
|
962
|
-
}
|
|
963
|
-
try {
|
|
964
|
-
return JSON.stringify(outputData, null, 2);
|
|
965
|
-
}
|
|
966
|
-
catch {
|
|
967
|
-
return '[Complex object - cannot serialize]';
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
Private.formatToolOutput = formatToolOutput;
|
|
1100
|
+
Private.readFileAttachment = readFileAttachment;
|
|
971
1101
|
})(Private || (Private = {}));
|
|
@@ -21,7 +21,7 @@ export function clearItem(translator) {
|
|
|
21
21
|
return {
|
|
22
22
|
element: (props) => {
|
|
23
23
|
const { model } = props;
|
|
24
|
-
const clearMessages = () => model.chatContext.clearMessages();
|
|
24
|
+
const clearMessages = async () => await model.chatContext.clearMessages();
|
|
25
25
|
const clearProps = {
|
|
26
26
|
...props,
|
|
27
27
|
clearMessages,
|
package/lib/components/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import React from 'react';
|
|
|
4
4
|
import { ISignal } from '@lumino/signaling';
|
|
5
5
|
import type { IAISettingsModel, ITokenUsage } from '../tokens';
|
|
6
6
|
/**
|
|
7
|
-
* Props for the
|
|
7
|
+
* Props for the UsageDisplay component.
|
|
8
8
|
*/
|
|
9
|
-
export interface
|
|
9
|
+
export interface IUsageDisplayProps {
|
|
10
10
|
/**
|
|
11
11
|
* The token usage changed signal
|
|
12
12
|
*/
|
|
@@ -25,24 +25,24 @@ export interface ITokenUsageDisplayProps {
|
|
|
25
25
|
translator: TranslationBundle;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* React component that displays
|
|
29
|
-
* Shows input/output token counts
|
|
30
|
-
* Only renders when token usage display is enabled in settings.
|
|
28
|
+
* React component that displays usage information.
|
|
29
|
+
* Shows input/output token counts and optional estimated context usage.
|
|
30
|
+
* Only renders when token or context usage display is enabled in settings.
|
|
31
31
|
*/
|
|
32
|
-
export declare const
|
|
32
|
+
export declare const UsageDisplay: React.FC<IUsageDisplayProps>;
|
|
33
33
|
/**
|
|
34
|
-
* JupyterLab widget wrapper for the
|
|
34
|
+
* JupyterLab widget wrapper for the UsageDisplay component.
|
|
35
35
|
* Extends ReactWidget to integrate with the JupyterLab widget system.
|
|
36
36
|
*/
|
|
37
|
-
export declare class
|
|
37
|
+
export declare class UsageWidget extends ReactWidget {
|
|
38
38
|
/**
|
|
39
|
-
* Creates a new
|
|
39
|
+
* Creates a new UsageWidget instance.
|
|
40
40
|
* @param options - Configuration options containing required models
|
|
41
41
|
*/
|
|
42
|
-
constructor(options:
|
|
42
|
+
constructor(options: IUsageDisplayProps);
|
|
43
43
|
/**
|
|
44
44
|
* Renders the React component within the widget.
|
|
45
|
-
* @returns The
|
|
45
|
+
* @returns The UsageDisplay React element
|
|
46
46
|
*/
|
|
47
47
|
protected render(): React.ReactElement;
|
|
48
48
|
private _options;
|