Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,8 @@ dmypy.json

# Yarn cache
.yarn/

# VSCode settings
*.code-workspace
.history/
.vscode/
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,20 @@ The `jlpm` command is JupyterLab's pinned version of
`yarn` or `npm` in lieu of `jlpm` below.

```bash
# Clone the repo to your local environment
# Clone the repo to your local environment, eg
gh repo clone jtpio/jupyterlab-ai-commands

# Change directory to the jupyterlab_ai_commands directory
cd jupyterlab-ai-commands

# Install package in development mode
pip install -e "."
# or
uv pip install -e "."

# Link your development version of the extension with JupyterLab
jupyter labextension develop . --overwrite

# Rebuild extension Typescript source after making changes
jlpm build
```
Expand All @@ -147,6 +155,7 @@ You can watch the source directory and run JupyterLab at the same time in differ
```bash
# Watch the source directory in one terminal, automatically rebuilding when needed
jlpm watch

# Run JupyterLab in another terminal
jupyter lab
```
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@types/json-schema": "^7.0.11",
"@types/react": "^18.0.26",
"@types/react-addons-linked-state-mixin": "^0.14.22",
"@types/w3c-image-capture": "^1.0.11",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"css-loader": "^6.7.1",
Expand Down
68 changes: 68 additions & 0 deletions src/notebook-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,73 @@ function registerSaveNotebookCommand(
commands.addCommand(command.id, command);
}

/**
* Save a specific notebook to disk
*/
function registerScreenshotCommand(
commands: CommandRegistry,
docManager: IDocumentManager,
notebookTracker?: INotebookTracker
): void {
const command = {
id: 'jupyterlab-ai-commands:screenshot-window',
label: 'Take window screenshot',
caption: 'Take a screenshot of the current browser window, return as base64 png',
describedBy: {
args: {
notebookPath: {
description:
'Path to the notebook file. If not provided, uses the currently active notebook'
}
}
},
execute: async (args: any) => {
const { notebookPath } = args;

const currentWidget = await getNotebookWidget(
notebookPath,
docManager,
notebookTracker
);
if (!currentWidget) {
return {
success: false,
error: notebookPath
? `Failed to open notebook at path: ${notebookPath}`
: 'No active notebook and no notebook path provided'
};
}

const displayMediaOptions = {
video: true,
audio: false,
};
const stream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
const [track] = stream.getVideoTracks();
const imageCapture = new ImageCapture(track);
const frame = await imageCapture.grabFrame();
track.stop();

const canvas = document.createElement('canvas');
canvas.width = frame.width;
canvas.height = frame.height;
const context = canvas.getContext('2d');
context?.drawImage(frame, 0, 0, frame.width, frame.height);

const dataUrl = canvas.toDataURL('image/png');
const base64Data = dataUrl.split(',')[1];

return {
success: true,
message: 'Notebook saved successfully',
windowScreenshot: base64Data,
};
}
};

commands.addCommand(command.id, command);
}

/**
* Options for registering notebook commands
*/
Expand Down Expand Up @@ -829,4 +896,5 @@ export function registerNotebookCommands(
registerRunCellCommand(commands, docManager, notebookTracker);
registerDeleteCellCommand(commands, docManager, notebookTracker);
registerSaveNotebookCommand(commands, docManager, notebookTracker);
registerScreenshotCommand(commands, docManager, notebookTracker);
}
17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,15 @@ __metadata:
languageName: node
linkType: hard

"@types/w3c-image-capture@npm:^1.0.11":
version: 1.0.11
resolution: "@types/w3c-image-capture@npm:1.0.11"
dependencies:
"@types/webrtc": "*"
checksum: 301fa282cf747aa74f22975515c55023f441bba1bcd545afaa49065ee6c09458d9deb4c68406a8dbbd8baace1b31aede502d1ea00f3d3b7cd607be5267f2110b
languageName: node
linkType: hard

"@types/webpack-sources@npm:^0.1.5":
version: 0.1.12
resolution: "@types/webpack-sources@npm:0.1.12"
Expand All @@ -1780,6 +1789,13 @@ __metadata:
languageName: node
linkType: hard

"@types/webrtc@npm:*":
version: 0.0.47
resolution: "@types/webrtc@npm:0.0.47"
checksum: b50f5fdc5d0df508aa3f2e7cdc1d53c4256e4f7fa7e01ccd1e59174fc1421e10e89a176a6b96eb401e45cdea5011a761af16170ce262621b8f98eb3f95d8c074
languageName: node
linkType: hard

"@types/yargs-parser@npm:*":
version: 21.0.3
resolution: "@types/yargs-parser@npm:21.0.3"
Expand Down Expand Up @@ -4180,6 +4196,7 @@ __metadata:
"@types/json-schema": ^7.0.11
"@types/react": ^18.0.26
"@types/react-addons-linked-state-mixin": ^0.14.22
"@types/w3c-image-capture": ^1.0.11
"@typescript-eslint/eslint-plugin": ^6.1.0
"@typescript-eslint/parser": ^6.1.0
css-loader: ^6.7.1
Expand Down
Loading