From 38ddbc0cbbf5f636cccc0fca2f1073b2af6601ef Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 6 Nov 2025 15:37:57 -0800 Subject: [PATCH 1/5] added vscode settings, etc to gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 396b5ef..c45b17a 100644 --- a/.gitignore +++ b/.gitignore @@ -123,3 +123,8 @@ dmypy.json # Yarn cache .yarn/ + +# VSCode settings +*.code-workspace +.history/ +.vscode/ From 651311253018232b6128832f46a54bbc4142726a Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 6 Nov 2025 15:38:35 -0800 Subject: [PATCH 2/5] minor fixes to dev install instructions --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d775a99..9ff3067 100644 --- a/README.md +++ b/README.md @@ -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 ``` @@ -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 ``` From f6f386283007f806466dd8fb2265d7a8bbd4e585 Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 6 Nov 2025 15:41:03 -0800 Subject: [PATCH 3/5] added skeleton of registerScreenshotCommand - for now it lives in notebook-commands.ts, may eventually break it out into window-commands.ts --- src/notebook-commands.ts | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/notebook-commands.ts b/src/notebook-commands.ts index fad929a..8e73e4e 100644 --- a/src/notebook-commands.ts +++ b/src/notebook-commands.ts @@ -791,6 +791,57 @@ 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:save-notebook', + label: 'Save Notebook', + caption: 'Save a specific notebook to disk', + 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' + }; + } + + await currentWidget.context.save(); + + return { + success: true, + message: 'Notebook saved successfully', + notebookName: currentWidget.title.label, + notebookPath: currentWidget.context.path + }; + } + }; + + commands.addCommand(command.id, command); +} + /** * Options for registering notebook commands */ @@ -829,4 +880,5 @@ export function registerNotebookCommands( registerRunCellCommand(commands, docManager, notebookTracker); registerDeleteCellCommand(commands, docManager, notebookTracker); registerSaveNotebookCommand(commands, docManager, notebookTracker); + registerScreenshotCommand(commands, docManager, notebookTracker); } From 900da2440432ffd9d9a2cfc48d3a59f71f9c4f43 Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 6 Nov 2025 16:14:46 -0800 Subject: [PATCH 4/5] added @types/w3c-image-capture --- package.json | 1 + yarn.lock | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/package.json b/package.json index df663e7..ba2588c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index d328bb8..59fd0f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -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" @@ -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 From f05d34e3dd51c51cda0a5eed15a7f08b7567c7da Mon Sep 17 00:00:00 2001 From: telamonian Date: Thu, 6 Nov 2025 16:15:42 -0800 Subject: [PATCH 5/5] added first prototype of `screenshot-window` command --- src/notebook-commands.ts | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/notebook-commands.ts b/src/notebook-commands.ts index 8e73e4e..5259262 100644 --- a/src/notebook-commands.ts +++ b/src/notebook-commands.ts @@ -800,9 +800,9 @@ function registerScreenshotCommand( notebookTracker?: INotebookTracker ): void { const command = { - id: 'jupyterlab-ai-commands:save-notebook', - label: 'Save Notebook', - caption: 'Save a specific notebook to disk', + 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: { @@ -828,13 +828,29 @@ function registerScreenshotCommand( }; } - await currentWidget.context.save(); + 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', - notebookName: currentWidget.title.label, - notebookPath: currentWidget.context.path + windowScreenshot: base64Data, }; } };