@notebook-intelligence/notebook-intelligence 1.2.2
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/LICENSE +674 -0
- package/README.md +151 -0
- package/lib/api.d.ts +47 -0
- package/lib/api.js +246 -0
- package/lib/chat-sidebar.d.ts +80 -0
- package/lib/chat-sidebar.js +1277 -0
- package/lib/handler.d.ts +8 -0
- package/lib/handler.js +36 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +1078 -0
- package/lib/markdown-renderer.d.ts +10 -0
- package/lib/markdown-renderer.js +60 -0
- package/lib/tokens.d.ts +93 -0
- package/lib/tokens.js +51 -0
- package/lib/utils.d.ts +17 -0
- package/lib/utils.js +163 -0
- package/package.json +219 -0
- package/schema/plugin.json +42 -0
- package/src/api.ts +333 -0
- package/src/chat-sidebar.tsx +2171 -0
- package/src/handler.ts +51 -0
- package/src/index.ts +1398 -0
- package/src/markdown-renderer.tsx +140 -0
- package/src/svg.d.ts +4 -0
- package/src/tokens.ts +114 -0
- package/src/utils.ts +204 -0
- package/style/base.css +590 -0
- package/style/icons/copilot-warning.svg +1 -0
- package/style/icons/copilot.svg +1 -0
- package/style/icons/copy.svg +1 -0
- package/style/icons/sparkles.svg +1 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import Markdown from 'react-markdown';
|
|
5
|
+
import remarkGfm from 'remark-gfm';
|
|
6
|
+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
7
|
+
import {
|
|
8
|
+
oneLight,
|
|
9
|
+
oneDark
|
|
10
|
+
} from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
|
11
|
+
import {
|
|
12
|
+
VscNewFile,
|
|
13
|
+
VscInsert,
|
|
14
|
+
VscCopy,
|
|
15
|
+
VscNotebook,
|
|
16
|
+
VscAdd
|
|
17
|
+
} from 'react-icons/vsc';
|
|
18
|
+
import { JupyterFrontEnd } from '@jupyterlab/application';
|
|
19
|
+
import { isDarkTheme } from './utils';
|
|
20
|
+
import { IActiveDocumentInfo } from './tokens';
|
|
21
|
+
|
|
22
|
+
type MarkdownRendererProps = {
|
|
23
|
+
children: string;
|
|
24
|
+
getApp: () => JupyterFrontEnd;
|
|
25
|
+
getActiveDocumentInfo(): IActiveDocumentInfo;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function MarkdownRenderer({
|
|
29
|
+
children: markdown,
|
|
30
|
+
getApp,
|
|
31
|
+
getActiveDocumentInfo
|
|
32
|
+
}: MarkdownRendererProps) {
|
|
33
|
+
const app = getApp();
|
|
34
|
+
const activeDocumentInfo = getActiveDocumentInfo();
|
|
35
|
+
const isNotebook = activeDocumentInfo.filename.endsWith('.ipynb');
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Markdown
|
|
39
|
+
remarkPlugins={[remarkGfm]}
|
|
40
|
+
components={{
|
|
41
|
+
code({ node, inline, className, children, getApp, ...props }: any) {
|
|
42
|
+
const match = /language-(\w+)/.exec(className || '');
|
|
43
|
+
const codeString = String(children).replace(/\n$/, '');
|
|
44
|
+
const language = match ? match[1] : 'text';
|
|
45
|
+
|
|
46
|
+
const handleCopyClick = () => {
|
|
47
|
+
navigator.clipboard.writeText(codeString);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleInsertAtCursorClick = () => {
|
|
51
|
+
app.commands.execute('notebook-intelligence:insert-at-cursor', {
|
|
52
|
+
language,
|
|
53
|
+
code: codeString
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleAddCodeAsNewCell = () => {
|
|
58
|
+
app.commands.execute('notebook-intelligence:add-code-as-new-cell', {
|
|
59
|
+
language,
|
|
60
|
+
code: codeString
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleCreateNewFileClick = () => {
|
|
65
|
+
app.commands.execute('notebook-intelligence:create-new-file', {
|
|
66
|
+
language,
|
|
67
|
+
code: codeString
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const handleCreateNewNotebookClick = () => {
|
|
72
|
+
app.commands.execute(
|
|
73
|
+
'notebook-intelligence:create-new-notebook-from-py',
|
|
74
|
+
{ language, code: codeString }
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return !inline && match ? (
|
|
79
|
+
<div>
|
|
80
|
+
<div className="code-block-header">
|
|
81
|
+
<div className="code-block-header-language">
|
|
82
|
+
<span>{language}</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div
|
|
85
|
+
className="code-block-header-button"
|
|
86
|
+
onClick={() => handleCopyClick()}
|
|
87
|
+
>
|
|
88
|
+
<VscCopy size={16} title="Copy to clipboard" />
|
|
89
|
+
<span>Copy</span>
|
|
90
|
+
</div>
|
|
91
|
+
<div
|
|
92
|
+
className="code-block-header-button"
|
|
93
|
+
onClick={() => handleInsertAtCursorClick()}
|
|
94
|
+
>
|
|
95
|
+
<VscInsert size={16} title="Insert at cursor" />
|
|
96
|
+
</div>
|
|
97
|
+
{isNotebook && (
|
|
98
|
+
<div
|
|
99
|
+
className="code-block-header-button"
|
|
100
|
+
onClick={() => handleAddCodeAsNewCell()}
|
|
101
|
+
>
|
|
102
|
+
<VscAdd size={16} title="Add as new cell" />
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
<div
|
|
106
|
+
className="code-block-header-button"
|
|
107
|
+
onClick={() => handleCreateNewFileClick()}
|
|
108
|
+
>
|
|
109
|
+
<VscNewFile size={16} title="New file" />
|
|
110
|
+
</div>
|
|
111
|
+
{language === 'python' && (
|
|
112
|
+
<div
|
|
113
|
+
className="code-block-header-button"
|
|
114
|
+
onClick={() => handleCreateNewNotebookClick()}
|
|
115
|
+
>
|
|
116
|
+
<VscNotebook size={16} title="New notebook" />
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
<SyntaxHighlighter
|
|
121
|
+
style={isDarkTheme() ? oneDark : oneLight}
|
|
122
|
+
PreTag="div"
|
|
123
|
+
language={language}
|
|
124
|
+
{...props}
|
|
125
|
+
>
|
|
126
|
+
{codeString}
|
|
127
|
+
</SyntaxHighlighter>
|
|
128
|
+
</div>
|
|
129
|
+
) : (
|
|
130
|
+
<code className={className} {...props}>
|
|
131
|
+
{children}
|
|
132
|
+
</code>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
{markdown}
|
|
138
|
+
</Markdown>
|
|
139
|
+
);
|
|
140
|
+
}
|
package/src/svg.d.ts
ADDED
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
|
|
3
|
+
import { Widget } from '@lumino/widgets';
|
|
4
|
+
import { CodeEditor } from '@jupyterlab/codeeditor';
|
|
5
|
+
import { Token } from '@lumino/coreutils';
|
|
6
|
+
|
|
7
|
+
export interface IActiveDocumentInfo {
|
|
8
|
+
activeWidget: Widget | null;
|
|
9
|
+
language: string;
|
|
10
|
+
filename: string;
|
|
11
|
+
filePath: string;
|
|
12
|
+
activeCellIndex: number;
|
|
13
|
+
selection?: CodeEditor.IRange;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IChatCompletionResponseEmitter {
|
|
17
|
+
emit: (response: any) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export enum RequestDataType {
|
|
21
|
+
ChatRequest = 'chat-request',
|
|
22
|
+
ChatUserInput = 'chat-user-input',
|
|
23
|
+
ClearChatHistory = 'clear-chat-history',
|
|
24
|
+
RunUICommandResponse = 'run-ui-command-response',
|
|
25
|
+
GenerateCode = 'generate-code',
|
|
26
|
+
CancelChatRequest = 'cancel-chat-request',
|
|
27
|
+
InlineCompletionRequest = 'inline-completion-request',
|
|
28
|
+
CancelInlineCompletionRequest = 'cancel-inline-completion-request'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export enum BackendMessageType {
|
|
32
|
+
StreamMessage = 'stream-message',
|
|
33
|
+
StreamEnd = 'stream-end',
|
|
34
|
+
RunUICommand = 'run-ui-command'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export enum ResponseStreamDataType {
|
|
38
|
+
LLMRaw = 'llm-raw',
|
|
39
|
+
Markdown = 'markdown',
|
|
40
|
+
MarkdownPart = 'markdown-part',
|
|
41
|
+
HTMLFrame = 'html-frame',
|
|
42
|
+
Button = 'button',
|
|
43
|
+
Anchor = 'anchor',
|
|
44
|
+
Progress = 'progress',
|
|
45
|
+
Confirmation = 'confirmation'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export enum ContextType {
|
|
49
|
+
Custom = 'custom',
|
|
50
|
+
CurrentFile = 'current-file'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface IContextItem {
|
|
54
|
+
type: ContextType;
|
|
55
|
+
content: string;
|
|
56
|
+
currentCellContents: ICellContents;
|
|
57
|
+
filePath?: string;
|
|
58
|
+
cellIndex?: number;
|
|
59
|
+
startLine?: number;
|
|
60
|
+
endLine?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ICellContents {
|
|
64
|
+
input: string;
|
|
65
|
+
output: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface IChatParticipant {
|
|
69
|
+
id: string;
|
|
70
|
+
name: string;
|
|
71
|
+
description: string;
|
|
72
|
+
iconPath: string;
|
|
73
|
+
commands: string[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const GITHUB_COPILOT_PROVIDER_ID = 'github-copilot';
|
|
77
|
+
|
|
78
|
+
export enum TelemetryEventType {
|
|
79
|
+
InlineCompletionRequest = 'inline-completion-request',
|
|
80
|
+
ExplainThisRequest = 'explain-this-request',
|
|
81
|
+
FixThisCodeRequest = 'fix-this-code-request',
|
|
82
|
+
ExplainThisOutputRequest = 'explain-this-output-request',
|
|
83
|
+
TroubleshootThisOutputRequest = 'troubleshoot-this-output-request',
|
|
84
|
+
GenerateCodeRequest = 'generate-code-request',
|
|
85
|
+
ChatRequest = 'chat-request',
|
|
86
|
+
InlineChatRequest = 'inline-chat-request',
|
|
87
|
+
ChatResponse = 'chat-response',
|
|
88
|
+
InlineChatResponse = 'inline-chat-response',
|
|
89
|
+
InlineCompletionResponse = 'inline-completion-response'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ITelemetryEvent {
|
|
93
|
+
type: TelemetryEventType;
|
|
94
|
+
data?: any;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ITelemetryListener {
|
|
98
|
+
get name(): string;
|
|
99
|
+
onTelemetryEvent: (event: ITelemetryEvent) => void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ITelemetryEmitter {
|
|
103
|
+
emitTelemetryEvent(event: ITelemetryEvent): void;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const INotebookIntelligence = new Token<INotebookIntelligence>(
|
|
107
|
+
'@notebook-intelligence/notebook-intelligence',
|
|
108
|
+
'AI coding assistant for JupyterLab.'
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
export interface INotebookIntelligence {
|
|
112
|
+
registerTelemetryListener: (listener: ITelemetryListener) => void;
|
|
113
|
+
unregisterTelemetryListener: (listener: ITelemetryListener) => void;
|
|
114
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
|
|
3
|
+
import { CodeCell } from '@jupyterlab/cells';
|
|
4
|
+
import { PartialJSONObject } from '@lumino/coreutils';
|
|
5
|
+
import { CodeEditor } from '@jupyterlab/codeeditor';
|
|
6
|
+
import { encoding_for_model } from 'tiktoken';
|
|
7
|
+
import { NotebookPanel } from '@jupyterlab/notebook';
|
|
8
|
+
|
|
9
|
+
const tiktoken_encoding = encoding_for_model('gpt-4o');
|
|
10
|
+
|
|
11
|
+
export function removeAnsiChars(str: string): string {
|
|
12
|
+
return str.replace(
|
|
13
|
+
// eslint-disable-next-line no-control-regex
|
|
14
|
+
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
|
15
|
+
''
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function waitForDuration(duration: number): Promise<void> {
|
|
20
|
+
return new Promise(resolve => {
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
resolve();
|
|
23
|
+
}, duration);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function moveCodeSectionBoundaryMarkersToNewLine(
|
|
28
|
+
source: string
|
|
29
|
+
): string {
|
|
30
|
+
const existingLines = source.split('\n');
|
|
31
|
+
const newLines = [];
|
|
32
|
+
for (const line of existingLines) {
|
|
33
|
+
if (line.length > 3 && line.startsWith('```')) {
|
|
34
|
+
newLines.push('```');
|
|
35
|
+
let remaining = line.substring(3);
|
|
36
|
+
if (remaining.startsWith('python')) {
|
|
37
|
+
if (remaining.length === 6) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
remaining = remaining.substring(6);
|
|
41
|
+
}
|
|
42
|
+
if (remaining.endsWith('```')) {
|
|
43
|
+
newLines.push(remaining.substring(0, remaining.length - 3));
|
|
44
|
+
newLines.push('```');
|
|
45
|
+
} else {
|
|
46
|
+
newLines.push(remaining);
|
|
47
|
+
}
|
|
48
|
+
} else if (line.length > 3 && line.endsWith('```')) {
|
|
49
|
+
newLines.push(line.substring(0, line.length - 3));
|
|
50
|
+
newLines.push('```');
|
|
51
|
+
} else {
|
|
52
|
+
newLines.push(line);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return newLines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function extractLLMGeneratedCode(code: string): string {
|
|
59
|
+
if (code.endsWith('```')) {
|
|
60
|
+
code = code.slice(0, -3);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const lines = code.split('\n');
|
|
64
|
+
if (lines.length < 2) {
|
|
65
|
+
return code;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const numLines = lines.length;
|
|
69
|
+
let startLine = -1;
|
|
70
|
+
let endLine = numLines;
|
|
71
|
+
|
|
72
|
+
for (let i = 0; i < numLines; i++) {
|
|
73
|
+
if (startLine === -1) {
|
|
74
|
+
if (lines[i].trimStart().startsWith('```')) {
|
|
75
|
+
startLine = i;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
if (lines[i].trimStart().startsWith('```')) {
|
|
80
|
+
endLine = i;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (startLine !== -1) {
|
|
87
|
+
return lines.slice(startLine + 1, endLine).join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return code;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function isDarkTheme(): boolean {
|
|
94
|
+
return document.body.getAttribute('data-jp-theme-light') === 'false';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function markdownToComment(source: string): string {
|
|
98
|
+
return source
|
|
99
|
+
.split('\n')
|
|
100
|
+
.map(line => `# ${line}`)
|
|
101
|
+
.join('\n');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function cellOutputAsText(cell: CodeCell): string {
|
|
105
|
+
let content = '';
|
|
106
|
+
const outputs = cell.outputArea.model.toJSON();
|
|
107
|
+
for (const output of outputs) {
|
|
108
|
+
if (output.output_type === 'execute_result') {
|
|
109
|
+
content +=
|
|
110
|
+
typeof output.data === 'object' && output.data !== null
|
|
111
|
+
? (output.data as PartialJSONObject)['text/plain']
|
|
112
|
+
: '' + '\n';
|
|
113
|
+
} else if (output.output_type === 'stream') {
|
|
114
|
+
content += output.text + '\n';
|
|
115
|
+
} else if (output.output_type === 'error') {
|
|
116
|
+
if (Array.isArray(output.traceback)) {
|
|
117
|
+
content += output.ename + ': ' + output.evalue + '\n';
|
|
118
|
+
content +=
|
|
119
|
+
output.traceback
|
|
120
|
+
.map(item => removeAnsiChars(item as string))
|
|
121
|
+
.join('\n') + '\n';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return content;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function getTokenCount(source: string): number {
|
|
130
|
+
const tokens = tiktoken_encoding.encode(source);
|
|
131
|
+
return tokens.length;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function compareSelectionPoints(
|
|
135
|
+
lhs: CodeEditor.IPosition,
|
|
136
|
+
rhs: CodeEditor.IPosition
|
|
137
|
+
): boolean {
|
|
138
|
+
return lhs.line === rhs.line && lhs.column === rhs.column;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function compareSelections(
|
|
142
|
+
lhs: CodeEditor.IRange,
|
|
143
|
+
rhs: CodeEditor.IRange
|
|
144
|
+
): boolean {
|
|
145
|
+
// if one undefined
|
|
146
|
+
if ((!lhs || !rhs) && !(!lhs && !rhs)) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
lhs === rhs ||
|
|
152
|
+
(compareSelectionPoints(lhs.start, rhs.start) &&
|
|
153
|
+
compareSelectionPoints(lhs.end, rhs.end))
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function isSelectionEmpty(selection: CodeEditor.IRange): boolean {
|
|
158
|
+
return (
|
|
159
|
+
selection.start.line === selection.end.line &&
|
|
160
|
+
selection.start.column === selection.end.column
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function getSelectionInEditor(editor: CodeEditor.IEditor): string {
|
|
165
|
+
const selection = editor.getSelection();
|
|
166
|
+
const startOffset = editor.getOffsetAt(selection.start);
|
|
167
|
+
const endOffset = editor.getOffsetAt(selection.end);
|
|
168
|
+
return editor.model.sharedModel.getSource().substring(startOffset, endOffset);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getWholeNotebookContent(np: NotebookPanel): string {
|
|
172
|
+
let content = '';
|
|
173
|
+
for (const cell of np.content.widgets) {
|
|
174
|
+
const cellModel = cell.model.sharedModel;
|
|
175
|
+
if (cellModel.cell_type === 'code') {
|
|
176
|
+
content += cellModel.source + '\n';
|
|
177
|
+
} else if (cellModel.cell_type === 'markdown') {
|
|
178
|
+
content += markdownToComment(cellModel.source) + '\n';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return content;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function applyCodeToSelectionInEditor(
|
|
186
|
+
editor: CodeEditor.IEditor,
|
|
187
|
+
code: string
|
|
188
|
+
) {
|
|
189
|
+
const selection = editor.getSelection();
|
|
190
|
+
const startOffset = editor.getOffsetAt(selection.start);
|
|
191
|
+
const endOffset = editor.getOffsetAt(selection.end);
|
|
192
|
+
|
|
193
|
+
editor.model.sharedModel.updateSource(startOffset, endOffset, code);
|
|
194
|
+
const numAddedLines = code.split('\n').length;
|
|
195
|
+
const cursorLine = Math.min(
|
|
196
|
+
selection.start.line + numAddedLines - 1,
|
|
197
|
+
editor.lineCount - 1
|
|
198
|
+
);
|
|
199
|
+
const cursorColumn = editor.getLine(cursorLine)?.length || 0;
|
|
200
|
+
editor.setCursorPosition({
|
|
201
|
+
line: cursorLine,
|
|
202
|
+
column: cursorColumn
|
|
203
|
+
});
|
|
204
|
+
}
|