@jupyterlite/ai 0.9.0-a3 → 0.9.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/README.md +20 -89
- package/lib/agent.d.ts +10 -4
- package/lib/agent.js +30 -17
- package/lib/chat-model.d.ts +6 -0
- package/lib/chat-model.js +144 -17
- package/lib/completion/completion-provider.js +1 -13
- package/lib/components/completion-status.d.ts +20 -0
- package/lib/components/completion-status.js +51 -0
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/model-select.js +1 -2
- package/lib/diff-manager.d.ts +25 -0
- package/lib/diff-manager.js +60 -0
- package/lib/icons.d.ts +0 -1
- package/lib/icons.js +2 -6
- package/lib/index.d.ts +2 -2
- package/lib/index.js +54 -23
- package/lib/models/settings-model.d.ts +4 -0
- package/lib/models/settings-model.js +24 -2
- package/lib/providers/built-in-providers.d.ts +0 -4
- package/lib/providers/built-in-providers.js +17 -23
- package/lib/tokens.d.ts +74 -0
- package/lib/tokens.js +4 -0
- package/lib/tools/commands.js +36 -35
- package/lib/tools/file.d.ts +10 -1
- package/lib/tools/file.js +235 -146
- package/lib/tools/notebook.d.ts +2 -3
- package/lib/tools/notebook.js +11 -11
- package/lib/widgets/ai-settings.js +78 -13
- package/lib/widgets/provider-config-dialog.js +15 -8
- package/package.json +5 -3
- package/schema/settings-model.json +25 -0
- package/src/agent.ts +35 -20
- package/src/chat-model.ts +182 -19
- package/src/completion/completion-provider.ts +1 -14
- package/src/components/completion-status.tsx +79 -0
- package/src/components/index.ts +1 -0
- package/src/components/model-select.tsx +0 -3
- package/src/diff-manager.ts +81 -0
- package/src/icons.ts +2 -7
- package/src/index.ts +74 -24
- package/src/models/settings-model.ts +28 -2
- package/src/providers/built-in-providers.ts +17 -24
- package/src/tokens.ts +78 -0
- package/src/tools/commands.ts +45 -40
- package/src/tools/file.ts +295 -164
- package/src/tools/notebook.ts +13 -14
- package/src/widgets/ai-settings.tsx +184 -35
- package/src/widgets/provider-config-dialog.tsx +43 -16
- package/style/base.css +14 -0
package/src/chat-model.ts
CHANGED
|
@@ -24,6 +24,8 @@ import { AISettingsModel } from './models/settings-model';
|
|
|
24
24
|
|
|
25
25
|
import { ITokenUsage } from './tokens';
|
|
26
26
|
|
|
27
|
+
import * as nbformat from '@jupyterlab/nbformat';
|
|
28
|
+
|
|
27
29
|
/**
|
|
28
30
|
* AI Chat Model implementation that provides chat functionality with OpenAI agents,
|
|
29
31
|
* tool integration, and MCP server support.
|
|
@@ -138,7 +140,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
138
140
|
time: Date.now() / 1000,
|
|
139
141
|
type: 'msg',
|
|
140
142
|
raw_time: false,
|
|
141
|
-
attachments: this.input.attachments
|
|
143
|
+
attachments: [...this.input.attachments]
|
|
142
144
|
};
|
|
143
145
|
this.messageAdded(userMessage);
|
|
144
146
|
|
|
@@ -163,6 +165,9 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
163
165
|
const attachmentContents = await this._processAttachments(
|
|
164
166
|
this.input.attachments
|
|
165
167
|
);
|
|
168
|
+
// Clear attachments right after processing
|
|
169
|
+
this.input.clearAttachments();
|
|
170
|
+
|
|
166
171
|
if (attachmentContents.length > 0) {
|
|
167
172
|
enhancedMessage +=
|
|
168
173
|
'\n\n--- Attached Files ---\n' + attachmentContents.join('\n\n');
|
|
@@ -172,8 +177,6 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
172
177
|
this.updateWriters([{ user: this._getAIUser() }]);
|
|
173
178
|
|
|
174
179
|
await this._agentManager.generateResponse(enhancedMessage);
|
|
175
|
-
// Clear attachments after processing
|
|
176
|
-
this.input.clearAttachments();
|
|
177
180
|
} catch (error) {
|
|
178
181
|
const errorMessage: IChatMessage = {
|
|
179
182
|
body: `Error generating AI response: ${(error as Error).message}`,
|
|
@@ -279,10 +282,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
279
282
|
*/
|
|
280
283
|
private _onSettingsChanged(): void {
|
|
281
284
|
const config = this._settingsModel.config;
|
|
282
|
-
this.config = {
|
|
283
|
-
...config,
|
|
284
|
-
enableCodeToolbar: true
|
|
285
|
-
};
|
|
285
|
+
this.config = { ...config, enableCodeToolbar: true };
|
|
286
286
|
// Agent manager handles agent recreation automatically via its own settings listener
|
|
287
287
|
}
|
|
288
288
|
|
|
@@ -383,7 +383,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
383
383
|
<div class="jp-ai-tool-body">
|
|
384
384
|
<div class="jp-ai-tool-section">
|
|
385
385
|
<div class="jp-ai-tool-label">Input</div>
|
|
386
|
-
<pre class="jp-ai-tool-code"><code>${
|
|
386
|
+
<pre class="jp-ai-tool-code"><code>${event.data.input}</code></pre>
|
|
387
387
|
</div>
|
|
388
388
|
</div>
|
|
389
389
|
</details>`,
|
|
@@ -481,7 +481,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
481
481
|
<div class="jp-ai-tool-body">
|
|
482
482
|
<div class="jp-ai-tool-section">
|
|
483
483
|
<div class="jp-ai-tool-label">${assistantName} wants to execute this tool. Do you approve?</div>
|
|
484
|
-
<pre class="jp-ai-tool-code"><code>${
|
|
484
|
+
<pre class="jp-ai-tool-code"><code>${event.data.toolInput}</code></pre>
|
|
485
485
|
</div>
|
|
486
486
|
[APPROVAL_BUTTONS:${event.data.interruptionId}]
|
|
487
487
|
</div>
|
|
@@ -504,7 +504,9 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
504
504
|
|
|
505
505
|
${assistantName} wants to execute this tool. Do you approve?
|
|
506
506
|
|
|
507
|
-
|
|
507
|
+
\`\`\`json
|
|
508
|
+
${event.data.toolInput}
|
|
509
|
+
\`\`\`
|
|
508
510
|
|
|
509
511
|
[APPROVAL_BUTTONS:${event.data.interruptionId}]`,
|
|
510
512
|
sender: this._getAIUser(),
|
|
@@ -531,7 +533,7 @@ ${JSON.stringify(event.data.toolInput, null, 2)}
|
|
|
531
533
|
const toolsList = event.data.approvals
|
|
532
534
|
.map(
|
|
533
535
|
(info, index) =>
|
|
534
|
-
`**${index + 1}. ${info.toolName}**\n${
|
|
536
|
+
`**${index + 1}. ${info.toolName}**\n\`\`\`json\n${info.toolInput}\n\`\`\`\n`
|
|
535
537
|
)
|
|
536
538
|
.join('\n\n');
|
|
537
539
|
|
|
@@ -582,14 +584,22 @@ ${toolsList}
|
|
|
582
584
|
|
|
583
585
|
for (const attachment of attachments) {
|
|
584
586
|
try {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
)
|
|
587
|
+
if (attachment.type === 'notebook' && attachment.cells?.length) {
|
|
588
|
+
const cellContents = await this._readNotebookCells(attachment);
|
|
589
|
+
if (cellContents) {
|
|
590
|
+
contents.push(cellContents);
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
const fileContent = await this._readFileAttachment(attachment);
|
|
594
|
+
if (fileContent) {
|
|
595
|
+
const fileExtension = PathExt.extname(
|
|
596
|
+
attachment.value
|
|
597
|
+
).toLowerCase();
|
|
598
|
+
const language = fileExtension === '.ipynb' ? 'json' : '';
|
|
599
|
+
contents.push(
|
|
600
|
+
`**File: ${attachment.value}**\n\`\`\`${language}\n${fileContent}\n\`\`\``
|
|
601
|
+
);
|
|
602
|
+
}
|
|
593
603
|
}
|
|
594
604
|
} catch (error) {
|
|
595
605
|
console.warn(`Failed to read attachment ${attachment.value}:`, error);
|
|
@@ -600,6 +610,159 @@ ${toolsList}
|
|
|
600
610
|
return contents;
|
|
601
611
|
}
|
|
602
612
|
|
|
613
|
+
/**
|
|
614
|
+
* Reads the content of a notebook cell.
|
|
615
|
+
* @param attachment The notebook attachment to read
|
|
616
|
+
* @returns Cell content as string or null if unable to read
|
|
617
|
+
*/
|
|
618
|
+
private async _readNotebookCells(
|
|
619
|
+
attachment: IAttachment
|
|
620
|
+
): Promise<string | null> {
|
|
621
|
+
if (attachment.type !== 'notebook' || !attachment.cells) {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
try {
|
|
626
|
+
const model = await this.input.documentManager?.services.contents.get(
|
|
627
|
+
attachment.value
|
|
628
|
+
);
|
|
629
|
+
if (!model || model.type !== 'notebook') {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const kernelLang =
|
|
634
|
+
model.content?.metadata?.language_info?.name ||
|
|
635
|
+
model.content?.metadata?.kernelspec?.language ||
|
|
636
|
+
'text';
|
|
637
|
+
|
|
638
|
+
const selectedCells = attachment.cells
|
|
639
|
+
.map(cellInfo => {
|
|
640
|
+
const cell = model.content.cells.find(
|
|
641
|
+
(c: any) => c.id === cellInfo.id
|
|
642
|
+
);
|
|
643
|
+
if (!cell) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const code = cell.source || '';
|
|
648
|
+
const cellType = cell.cell_type;
|
|
649
|
+
const lang = cellType === 'code' ? kernelLang : cellType;
|
|
650
|
+
|
|
651
|
+
const DISPLAY_PRIORITY = [
|
|
652
|
+
'application/vnd.jupyter.widget-view+json',
|
|
653
|
+
'application/javascript',
|
|
654
|
+
'text/html',
|
|
655
|
+
'image/svg+xml',
|
|
656
|
+
'image/png',
|
|
657
|
+
'image/jpeg',
|
|
658
|
+
'text/markdown',
|
|
659
|
+
'text/latex',
|
|
660
|
+
'text/plain'
|
|
661
|
+
];
|
|
662
|
+
|
|
663
|
+
function extractDisplay(data: any): string {
|
|
664
|
+
for (const mime of DISPLAY_PRIORITY) {
|
|
665
|
+
if (!(mime in data)) {
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const value = data[mime];
|
|
670
|
+
if (!value) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
switch (mime) {
|
|
675
|
+
case 'application/vnd.jupyter.widget-view+json':
|
|
676
|
+
return `Widget: ${(value as any).model_id ?? 'unknown model'}`;
|
|
677
|
+
|
|
678
|
+
case 'image/png':
|
|
679
|
+
return `}...)`;
|
|
680
|
+
|
|
681
|
+
case 'image/jpeg':
|
|
682
|
+
return `}...)`;
|
|
683
|
+
|
|
684
|
+
case 'image/svg+xml':
|
|
685
|
+
return String(value).slice(0, 500) + '...\n[svg truncated]';
|
|
686
|
+
|
|
687
|
+
case 'text/html':
|
|
688
|
+
return (
|
|
689
|
+
String(value).slice(0, 1000) +
|
|
690
|
+
(String(value).length > 1000 ? '\n...[truncated]' : '')
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
case 'text/markdown':
|
|
694
|
+
case 'text/latex':
|
|
695
|
+
case 'text/plain': {
|
|
696
|
+
let text = Array.isArray(value)
|
|
697
|
+
? value.join('')
|
|
698
|
+
: String(value);
|
|
699
|
+
if (text.length > 2000) {
|
|
700
|
+
text = text.slice(0, 2000) + '\n...[truncated]';
|
|
701
|
+
}
|
|
702
|
+
return text;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
default:
|
|
706
|
+
return JSON.stringify(value).slice(0, 2000);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return JSON.stringify(data).slice(0, 2000);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
let outputs = '';
|
|
714
|
+
if (cellType === 'code' && Array.isArray(cell.outputs)) {
|
|
715
|
+
outputs = cell.outputs
|
|
716
|
+
.map((output: nbformat.IOutput) => {
|
|
717
|
+
if (output.output_type === 'stream') {
|
|
718
|
+
return (output as nbformat.IStream).text;
|
|
719
|
+
} else if (output.output_type === 'error') {
|
|
720
|
+
const err = output as nbformat.IError;
|
|
721
|
+
return `${err.ename}: ${err.evalue}\n${(err.traceback || []).join('\n')}`;
|
|
722
|
+
} else if (
|
|
723
|
+
output.output_type === 'execute_result' ||
|
|
724
|
+
output.output_type === 'display_data'
|
|
725
|
+
) {
|
|
726
|
+
const data = (output as nbformat.IDisplayData).data;
|
|
727
|
+
if (!data) {
|
|
728
|
+
return '';
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
return extractDisplay(data);
|
|
732
|
+
} catch (e) {
|
|
733
|
+
console.error('Cannot extract cell output', e);
|
|
734
|
+
return '';
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return '';
|
|
738
|
+
})
|
|
739
|
+
.filter(Boolean)
|
|
740
|
+
.join('\n---\n');
|
|
741
|
+
|
|
742
|
+
if (outputs.length > 2000) {
|
|
743
|
+
outputs = outputs.slice(0, 2000) + '\n...[truncated]';
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return (
|
|
748
|
+
`**Cell [${cellInfo.id}] (${cellType}):**\n` +
|
|
749
|
+
`\`\`\`${lang}\n${code}\n\`\`\`` +
|
|
750
|
+
(outputs ? `\n**Outputs:**\n\`\`\`text\n${outputs}\n\`\`\`` : '')
|
|
751
|
+
);
|
|
752
|
+
})
|
|
753
|
+
.filter(Boolean)
|
|
754
|
+
.join('\n\n');
|
|
755
|
+
|
|
756
|
+
return `**Notebook: ${attachment.value}**\n${selectedCells}`;
|
|
757
|
+
} catch (error) {
|
|
758
|
+
console.warn(
|
|
759
|
+
`Failed to read notebook cells from ${attachment.value}:`,
|
|
760
|
+
error
|
|
761
|
+
);
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
603
766
|
/**
|
|
604
767
|
* Reads the content of a file attachment.
|
|
605
768
|
* @param attachment The file attachment to read
|
|
@@ -33,19 +33,6 @@ export interface IProviderCompletionConfig {
|
|
|
33
33
|
useFilterText?: boolean;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* Default system prompt for code completion
|
|
38
|
-
*/
|
|
39
|
-
const DEFAULT_COMPLETION_SYSTEM_PROMPT = `You are an AI code completion assistant. Complete the given code fragment with appropriate code.
|
|
40
|
-
Rules:
|
|
41
|
-
- Return only the completion text, no explanations or comments
|
|
42
|
-
- Do not include code block markers (\`\`\` or similar)
|
|
43
|
-
- Make completions contextually relevant to the surrounding code and notebook context
|
|
44
|
-
- Follow the language-specific conventions and style guidelines for the detected programming language
|
|
45
|
-
- Keep completions concise but functional
|
|
46
|
-
- Do not repeat the existing code that comes before the cursor
|
|
47
|
-
- Use variables, imports, functions, and other definitions from previous notebook cells when relevant`;
|
|
48
|
-
|
|
49
36
|
/**
|
|
50
37
|
* Default temperature for code completion (lower than chat for more deterministic results)
|
|
51
38
|
*/
|
|
@@ -86,7 +73,7 @@ export class AICompletionProvider implements IInlineCompletionProvider {
|
|
|
86
73
|
* Get the system prompt for the completion.
|
|
87
74
|
*/
|
|
88
75
|
get systemPrompt(): string {
|
|
89
|
-
return
|
|
76
|
+
return this._settingsModel.config.completionSystemPrompt;
|
|
90
77
|
}
|
|
91
78
|
|
|
92
79
|
/**
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { AISettingsModel } from '../models/settings-model';
|
|
3
|
+
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
4
|
+
import { jupyternautIcon } from '../icons';
|
|
5
|
+
|
|
6
|
+
const COMPLETION_STATUS_CLASS = 'jp-ai-completion-status';
|
|
7
|
+
const COMPLETION_DISABLED_CLASS = 'jp-ai-completion-disabled';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The completion status props.
|
|
11
|
+
*/
|
|
12
|
+
interface ICompletionStatusProps {
|
|
13
|
+
/**
|
|
14
|
+
* The settings model.
|
|
15
|
+
*/
|
|
16
|
+
settingsModel: AISettingsModel;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The completion status component.
|
|
21
|
+
*/
|
|
22
|
+
function CompletionStatus(props: ICompletionStatusProps): JSX.Element {
|
|
23
|
+
const [disabled, setDisabled] = useState<boolean>(true);
|
|
24
|
+
const [title, setTitle] = useState<string>('');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Handle changes in the settings.
|
|
28
|
+
*/
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const stateChanged = (model: AISettingsModel) => {
|
|
31
|
+
if (model.config.useSameProviderForChatAndCompleter) {
|
|
32
|
+
setDisabled(false);
|
|
33
|
+
setTitle(`Completion using ${model.getDefaultProvider()?.model}`);
|
|
34
|
+
} else if (model.config.activeCompleterProvider) {
|
|
35
|
+
setDisabled(false);
|
|
36
|
+
setTitle(
|
|
37
|
+
`Completion using ${model.getProvider(model.config.activeCompleterProvider)?.model}`
|
|
38
|
+
);
|
|
39
|
+
} else {
|
|
40
|
+
setDisabled(true);
|
|
41
|
+
setTitle('No completion');
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
props.settingsModel.stateChanged.connect(stateChanged);
|
|
46
|
+
|
|
47
|
+
stateChanged(props.settingsModel);
|
|
48
|
+
return () => {
|
|
49
|
+
props.settingsModel.stateChanged.disconnect(stateChanged);
|
|
50
|
+
};
|
|
51
|
+
}, [props.settingsModel]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<jupyternautIcon.react
|
|
55
|
+
className={disabled ? COMPLETION_DISABLED_CLASS : ''}
|
|
56
|
+
top={'2px'}
|
|
57
|
+
width={'16px'}
|
|
58
|
+
stylesheet={'statusBar'}
|
|
59
|
+
title={title}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The completion status widget that will be added to the status bar.
|
|
66
|
+
*/
|
|
67
|
+
export class CompletionStatusWidget extends ReactWidget {
|
|
68
|
+
constructor(options: ICompletionStatusProps) {
|
|
69
|
+
super();
|
|
70
|
+
this.addClass(COMPLETION_STATUS_CLASS);
|
|
71
|
+
this._props = options;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
render(): JSX.Element {
|
|
75
|
+
return <CompletionStatus {...this._props} />;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private _props: ICompletionStatusProps;
|
|
79
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -5,8 +5,6 @@ import React, { useCallback, useEffect, useState } from 'react';
|
|
|
5
5
|
import { AIChatModel } from '../chat-model';
|
|
6
6
|
import { AISettingsModel } from '../models/settings-model';
|
|
7
7
|
|
|
8
|
-
const SELECT_ITEM_CLASS = 'labai-model-select-item';
|
|
9
|
-
|
|
10
8
|
/**
|
|
11
9
|
* Properties for the model select component.
|
|
12
10
|
*/
|
|
@@ -187,7 +185,6 @@ export function ModelSelect(props: IModelSelectProps): JSX.Element {
|
|
|
187
185
|
{availableModels.map(({ provider, providerLabel, isSelected }) => (
|
|
188
186
|
<MenuItem
|
|
189
187
|
key={provider}
|
|
190
|
-
className={SELECT_ITEM_CLASS}
|
|
191
188
|
onClick={async e => {
|
|
192
189
|
await selectModel(provider);
|
|
193
190
|
// Prevent sending message on model selection
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
2
|
+
import { AISettingsModel } from './models/settings-model';
|
|
3
|
+
import {
|
|
4
|
+
IDiffManager,
|
|
5
|
+
IShowCellDiffParams,
|
|
6
|
+
IShowFileDiffParams
|
|
7
|
+
} from './tokens';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Command IDs for unified cell diffs
|
|
11
|
+
*/
|
|
12
|
+
const UNIFIED_DIFF_COMMAND_ID = 'jupyterlab-diff:unified-cell-diff';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Command IDs for split cell diffs
|
|
16
|
+
*/
|
|
17
|
+
const SPLIT_DIFF_COMMAND_ID = 'jupyterlab-diff:split-cell-diff';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Command ID for unified file diffs
|
|
21
|
+
*/
|
|
22
|
+
const UNIFIED_FILE_DIFF_COMMAND_ID = 'jupyterlab-diff:unified-file-diff';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Implementation of the diff manager
|
|
26
|
+
*/
|
|
27
|
+
export class DiffManager implements IDiffManager {
|
|
28
|
+
/**
|
|
29
|
+
* Construct a new DiffManager
|
|
30
|
+
*/
|
|
31
|
+
constructor(options: {
|
|
32
|
+
commands: CommandRegistry;
|
|
33
|
+
settingsModel: AISettingsModel;
|
|
34
|
+
}) {
|
|
35
|
+
this._commands = options.commands;
|
|
36
|
+
this._settingsModel = options.settingsModel;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Show diff between original and modified cell content
|
|
41
|
+
*/
|
|
42
|
+
async showCellDiff(params: IShowCellDiffParams): Promise<void> {
|
|
43
|
+
if (!this._settingsModel.config.showCellDiff) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const showDiffCommandId =
|
|
48
|
+
this._settingsModel.config.diffDisplayMode === 'unified'
|
|
49
|
+
? UNIFIED_DIFF_COMMAND_ID
|
|
50
|
+
: SPLIT_DIFF_COMMAND_ID;
|
|
51
|
+
|
|
52
|
+
await this._commands.execute(showDiffCommandId, {
|
|
53
|
+
originalSource: params.original,
|
|
54
|
+
newSource: params.modified,
|
|
55
|
+
cellId: params.cellId,
|
|
56
|
+
showActionButtons: params.showActionButtons ?? true,
|
|
57
|
+
openDiff: params.openDiff ?? true,
|
|
58
|
+
notebookPath: params.notebookPath
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Show diff between original and modified file content
|
|
64
|
+
*/
|
|
65
|
+
async showFileDiff(params: IShowFileDiffParams): Promise<void> {
|
|
66
|
+
if (!this._settingsModel.config.showFileDiff) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// File diffs only support unified view
|
|
71
|
+
await this._commands.execute(UNIFIED_FILE_DIFF_COMMAND_ID, {
|
|
72
|
+
originalSource: params.original,
|
|
73
|
+
newSource: params.modified,
|
|
74
|
+
filePath: params.filePath,
|
|
75
|
+
showActionButtons: params.showActionButtons ?? true
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private _commands: CommandRegistry;
|
|
80
|
+
private _settingsModel: AISettingsModel;
|
|
81
|
+
}
|
package/src/icons.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
export const labaiIcon = new LabIcon({
|
|
6
|
-
name: '@jupyterlite/ai:icon',
|
|
7
|
-
svgstr: labaiIconSvg
|
|
8
|
-
});
|
|
3
|
+
import jupyternautSvg from '../style/icons/jupyternaut-lite.svg';
|
|
9
4
|
|
|
10
5
|
export const jupyternautIcon = new LabIcon({
|
|
11
6
|
name: '@jupyterlite/ai:jupyternaut',
|
|
12
|
-
svgstr:
|
|
7
|
+
svgstr: jupyternautSvg
|
|
13
8
|
});
|
|
14
9
|
|
|
15
10
|
const AI_AVATAR_BASE64 = btoa(jupyternautIcon.svgstr);
|