@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.
@@ -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
@@ -0,0 +1,4 @@
1
+ declare module '*.svg' {
2
+ const value: string; // @ts-ignore
3
+ export default value;
4
+ }
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
+ }