From 79158abf7044605c5776e205dd171fe87fb64142 Mon Sep 17 00:00:00 2001 From: david qiu Date: Thu, 29 Aug 2024 13:18:34 -0700 Subject: [PATCH 1/2] add 'Generative AI' submenu (#971) --- .../src/components/chat-input/send-button.tsx | 3 +- .../src/contexts/active-cell-context.tsx | 2 +- packages/jupyter-ai/src/index.ts | 17 +- .../jupyter-ai/src/plugins/menu-plugin.ts | 158 ++++++++++++++++++ packages/jupyter-ai/src/selection-watcher.ts | 9 + packages/jupyter-ai/src/tokens.ts | 23 ++- 6 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 packages/jupyter-ai/src/plugins/menu-plugin.ts diff --git a/packages/jupyter-ai/src/components/chat-input/send-button.tsx b/packages/jupyter-ai/src/components/chat-input/send-button.tsx index 69ad3efc6..7dcf09ace 100644 --- a/packages/jupyter-ai/src/components/chat-input/send-button.tsx +++ b/packages/jupyter-ai/src/components/chat-input/send-button.tsx @@ -85,7 +85,8 @@ export function SendButton(props: SendButtonProps): JSX.Element { if (activeCell.exists) { props.onSend({ type: 'cell', - source: activeCell.manager.getContent(false).source + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + source: activeCell.manager.getContent(false)!.source }); closeMenu(); return; diff --git a/packages/jupyter-ai/src/contexts/active-cell-context.tsx b/packages/jupyter-ai/src/contexts/active-cell-context.tsx index a8a6ebcaf..72e93a8ca 100644 --- a/packages/jupyter-ai/src/contexts/active-cell-context.tsx +++ b/packages/jupyter-ai/src/contexts/active-cell-context.tsx @@ -83,7 +83,7 @@ export class ActiveCellManager { * `ActiveCellContentWithError` object that describes both the active cell and * the error output. */ - getContent(withError: false): CellContent; + getContent(withError: false): CellContent | null; getContent(withError: true): CellWithErrorContent | null; getContent(withError = false): CellContent | CellWithErrorContent | null { const sharedModel = this._activeCell?.model.sharedModel; diff --git a/packages/jupyter-ai/src/index.ts b/packages/jupyter-ai/src/index.ts index e42091980..d6e52b576 100644 --- a/packages/jupyter-ai/src/index.ts +++ b/packages/jupyter-ai/src/index.ts @@ -18,10 +18,11 @@ import { ChatHandler } from './chat_handler'; import { buildErrorWidget } from './widgets/chat-error'; import { completionPlugin } from './completions'; import { statusItemPlugin } from './status'; -import { IJaiCompletionProvider, IJaiMessageFooter } from './tokens'; +import { IJaiCompletionProvider, IJaiCore, IJaiMessageFooter } from './tokens'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { ActiveCellManager } from './contexts/active-cell-context'; import { Signal } from '@lumino/signaling'; +import { menuPlugin } from './plugins/menu-plugin'; export type DocumentTracker = IWidgetTracker; @@ -35,9 +36,10 @@ export namespace CommandIDs { /** * Initialization data for the jupyter_ai extension. */ -const plugin: JupyterFrontEndPlugin = { +const plugin: JupyterFrontEndPlugin = { id: '@jupyter-ai/core:plugin', autoStart: true, + requires: [IRenderMimeRegistry], optional: [ IGlobalAwareness, ILayoutRestorer, @@ -45,7 +47,7 @@ const plugin: JupyterFrontEndPlugin = { IJaiCompletionProvider, IJaiMessageFooter ], - requires: [IRenderMimeRegistry], + provides: IJaiCore, activate: async ( app: JupyterFrontEnd, rmRegistry: IRenderMimeRegistry, @@ -114,7 +116,14 @@ const plugin: JupyterFrontEndPlugin = { }, label: 'Focus the jupyter-ai chat' }); + + return { + activeCellManager, + chatHandler, + chatWidget, + selectionWatcher + }; } }; -export default [plugin, statusItemPlugin, completionPlugin]; +export default [plugin, statusItemPlugin, completionPlugin, menuPlugin]; diff --git a/packages/jupyter-ai/src/plugins/menu-plugin.ts b/packages/jupyter-ai/src/plugins/menu-plugin.ts new file mode 100644 index 000000000..8994a552d --- /dev/null +++ b/packages/jupyter-ai/src/plugins/menu-plugin.ts @@ -0,0 +1,158 @@ +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; + +import { IJaiCore } from '../tokens'; +import { AiService } from '../handler'; +import { Menu } from '@lumino/widgets'; +import { CommandRegistry } from '@lumino/commands'; + +export namespace CommandIDs { + export const explain = 'jupyter-ai:explain'; + export const fix = 'jupyter-ai:fix'; + export const optimize = 'jupyter-ai:optimize'; + export const refactor = 'jupyter-ai:refactor'; +} + +/** + * Optional plugin that adds a "Generative AI" submenu to the context menu. + * These implement UI shortcuts that explain, fix, refactor, or optimize code in + * a notebook or file. + * + * **This plugin is experimental and may be removed in a future release.** + */ +export const menuPlugin: JupyterFrontEndPlugin = { + id: '@jupyter-ai/core:menu-plugin', + autoStart: true, + requires: [IJaiCore], + activate: (app: JupyterFrontEnd, jaiCore: IJaiCore) => { + const { activeCellManager, chatHandler, chatWidget, selectionWatcher } = + jaiCore; + + function activateChatSidebar() { + app.shell.activateById(chatWidget.id); + } + + function getSelection(): AiService.Selection | null { + const textSelection = selectionWatcher.selection; + const activeCell = activeCellManager.getContent(false); + const selection: AiService.Selection | null = textSelection + ? { type: 'text', source: textSelection.text } + : activeCell + ? { type: 'cell', source: activeCell.source } + : null; + + return selection; + } + + function buildLabelFactory(baseLabel: string): () => string { + return () => { + const textSelection = selectionWatcher.selection; + const activeCell = activeCellManager.getContent(false); + + return textSelection + ? `${baseLabel} (${textSelection.numLines} lines selected)` + : activeCell + ? `${baseLabel} (1 active cell)` + : baseLabel; + }; + } + + // register commands + const menuCommands = new CommandRegistry(); + menuCommands.addCommand(CommandIDs.explain, { + execute: () => { + const selection = getSelection(); + if (!selection) { + return; + } + + activateChatSidebar(); + chatHandler.sendMessage({ + prompt: 'Explain the code below.', + selection + }); + }, + label: buildLabelFactory('Explain code'), + isEnabled: () => !!getSelection() + }); + menuCommands.addCommand(CommandIDs.fix, { + execute: () => { + const activeCellWithError = activeCellManager.getContent(true); + if (!activeCellWithError) { + return; + } + + chatHandler.sendMessage({ + prompt: '/fix', + selection: { + type: 'cell-with-error', + error: activeCellWithError.error, + source: activeCellWithError.source + } + }); + }, + label: () => { + const activeCellWithError = activeCellManager.getContent(true); + return activeCellWithError + ? 'Fix code cell (1 error cell)' + : 'Fix code cell (no error cell)'; + }, + isEnabled: () => { + const activeCellWithError = activeCellManager.getContent(true); + return !!activeCellWithError; + } + }); + menuCommands.addCommand(CommandIDs.optimize, { + execute: () => { + const selection = getSelection(); + if (!selection) { + return; + } + + activateChatSidebar(); + chatHandler.sendMessage({ + prompt: 'Optimize the code below.', + selection + }); + }, + label: buildLabelFactory('Optimize code'), + isEnabled: () => !!getSelection() + }); + menuCommands.addCommand(CommandIDs.refactor, { + execute: () => { + const selection = getSelection(); + if (!selection) { + return; + } + + activateChatSidebar(); + chatHandler.sendMessage({ + prompt: 'Refactor the code below.', + selection + }); + }, + label: buildLabelFactory('Refactor code'), + isEnabled: () => !!getSelection() + }); + + // add commands as a context menu item containing a "Generative AI" submenu + const submenu = new Menu({ + commands: menuCommands + }); + submenu.id = 'jupyter-ai:submenu'; + submenu.title.label = 'Generative AI'; + submenu.addItem({ command: CommandIDs.explain }); + submenu.addItem({ command: CommandIDs.fix }); + submenu.addItem({ command: CommandIDs.optimize }); + submenu.addItem({ command: CommandIDs.refactor }); + + app.contextMenu.addItem({ + type: 'submenu', + selector: '.jp-Editor', + rank: 1, + submenu + }); + } +}; diff --git a/packages/jupyter-ai/src/selection-watcher.ts b/packages/jupyter-ai/src/selection-watcher.ts index 8dd7df586..9cbb67f31 100644 --- a/packages/jupyter-ai/src/selection-watcher.ts +++ b/packages/jupyter-ai/src/selection-watcher.ts @@ -76,6 +76,7 @@ function getTextSelection(widget: Widget | null): Selection | null { start, end, text, + numLines: text.split('\n').length, widgetId: widget.id, ...(cellId && { cellId @@ -88,6 +89,10 @@ export type Selection = CodeEditor.ITextSelection & { * The text within the selection as a string. */ text: string; + /** + * Number of lines contained by the text selection. + */ + numLines: number; /** * The ID of the document widget in which the selection was made. */ @@ -109,6 +114,10 @@ export class SelectionWatcher { setInterval(this._poll.bind(this), 200); } + get selection(): Selection | null { + return this._selection; + } + get selectionChanged(): Signal { return this._selectionChanged; } diff --git a/packages/jupyter-ai/src/tokens.ts b/packages/jupyter-ai/src/tokens.ts index efcada10f..9caf1362d 100644 --- a/packages/jupyter-ai/src/tokens.ts +++ b/packages/jupyter-ai/src/tokens.ts @@ -1,8 +1,12 @@ import React from 'react'; import { Token } from '@lumino/coreutils'; import { ISignal } from '@lumino/signaling'; -import type { IRankedMenu } from '@jupyterlab/ui-components'; +import type { IRankedMenu, ReactWidget } from '@jupyterlab/ui-components'; + import { AiService } from './handler'; +import { ChatHandler } from './chat_handler'; +import { ActiveCellManager } from './contexts/active-cell-context'; +import { SelectionWatcher } from './selection-watcher'; export interface IJaiStatusItem { addItem(item: IRankedMenu.IItemOptions): void; @@ -46,3 +50,20 @@ export const IJaiMessageFooter = new Token( 'jupyter_ai:IJaiMessageFooter', 'Optional component that is used to render a footer on each Jupyter AI chat message, when provided.' ); + +export interface IJaiCore { + chatWidget: ReactWidget; + chatHandler: ChatHandler; + activeCellManager: ActiveCellManager; + selectionWatcher: SelectionWatcher; +} + +/** + * The Jupyter AI core provider token. Frontend plugins that want to extend the + * Jupyter AI frontend by adding features which send messages or observe the + * current text selection & active cell should require this plugin. + */ +export const IJaiCore = new Token( + 'jupyter_ai:core', + 'The core implementation of the frontend.' +); From 4b45ec23f3e16a0fa29718eefaf37064f59cafe6 Mon Sep 17 00:00:00 2001 From: dlqqq Date: Thu, 29 Aug 2024 20:33:59 +0000 Subject: [PATCH 2/2] Publish 2.22.0 SHA256 hashes: jupyter-ai-core-2.22.0.tgz: 2bf93b4b567314a410f360b4a6bbd0fd69b316c75fd1036485f840a761bcc928 jupyter_ai-2.22.0-py3-none-any.whl: 383e5d576fd032166bdd3e3d95396ce7622415821f7ead4364874c9b097b5838 jupyter_ai-2.22.0.tar.gz: aa827b3040646fd2492dd3227d521c85fa5207edbb09b2262590d59562e229a1 jupyter_ai_magics-2.22.0-py3-none-any.whl: 31f8b3dd632a538e1d26b14d4c1426a3eb51706f0885f6af2cf34f09c37bce1a jupyter_ai_magics-2.22.0.tar.gz: 9205abc585c6006791f838afc131fb8e4618e66f5b707bf80d1bad7070272d9c --- CHANGELOG.md | 27 +++++++++++++++++++++++-- lerna.json | 2 +- package.json | 2 +- packages/jupyter-ai-magics/package.json | 2 +- packages/jupyter-ai-test/package.json | 2 +- packages/jupyter-ai/package.json | 2 +- 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 160b3b760..d1e7b9c9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ +## 2.22.0 + +([Full Changelog](https://github.com/jupyterlab/jupyter-ai/compare/@jupyter-ai/core@2.21.0...79158abf7044605c5776e205dd171fe87fb64142)) + +### Enhancements made + +- Add 'Generative AI' submenu [#971](https://github.com/jupyterlab/jupyter-ai/pull/971) ([@dlqqq](https://github.com/dlqqq)) +- Add Gemini 1.5 to the list of chat options [#964](https://github.com/jupyterlab/jupyter-ai/pull/964) ([@trducng](https://github.com/trducng)) +- Allow configuring a default model for cell magics (and line error magic) [#962](https://github.com/jupyterlab/jupyter-ai/pull/962) ([@krassowski](https://github.com/krassowski)) +- Make chat memory size traitlet configurable + /clear to reset memory [#943](https://github.com/jupyterlab/jupyter-ai/pull/943) ([@michaelchia](https://github.com/michaelchia)) + +### Maintenance and upkeep improvements + +### Documentation improvements + +- Update documentation to cover installation of all dependencies [#961](https://github.com/jupyterlab/jupyter-ai/pull/961) ([@srdas](https://github.com/srdas)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyterlab/jupyter-ai/graphs/contributors?from=2024-08-19&to=2024-08-29&type=c)) + +[@dlqqq](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Adlqqq+updated%3A2024-08-19..2024-08-29&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Akrassowski+updated%3A2024-08-19..2024-08-29&type=Issues) | [@michaelchia](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Amichaelchia+updated%3A2024-08-19..2024-08-29&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Apre-commit-ci+updated%3A2024-08-19..2024-08-29&type=Issues) | [@srdas](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Asrdas+updated%3A2024-08-19..2024-08-29&type=Issues) | [@trducng](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Atrducng+updated%3A2024-08-19..2024-08-29&type=Issues) + + + ## 2.21.0 ([Full Changelog](https://github.com/jupyterlab/jupyter-ai/compare/@jupyter-ai/core@2.20.0...83e368b9d04904f9eb0ad4b1f0759bf3b7bbc93d)) @@ -32,8 +57,6 @@ [@andrewfulton9](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Aandrewfulton9+updated%3A2024-07-29..2024-08-19&type=Issues) | [@dlqqq](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Adlqqq+updated%3A2024-07-29..2024-08-19&type=Issues) | [@gabrielkoo](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Agabrielkoo+updated%3A2024-07-29..2024-08-19&type=Issues) | [@gsrikant7](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Agsrikant7+updated%3A2024-07-29..2024-08-19&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Akrassowski+updated%3A2024-07-29..2024-08-19&type=Issues) | [@michaelchia](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Amichaelchia+updated%3A2024-07-29..2024-08-19&type=Issues) | [@srdas](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyter-ai+involves%3Asrdas+updated%3A2024-07-29..2024-08-19&type=Issues) - - ## 2.20.0 ([Full Changelog](https://github.com/jupyterlab/jupyter-ai/compare/@jupyter-ai/core@2.19.1...79d66daefa2dc2f8a47b55712d7b812ee23acda4)) diff --git a/lerna.json b/lerna.json index 33633c5f5..5c20bb775 100644 --- a/lerna.json +++ b/lerna.json @@ -1,7 +1,7 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useWorkspaces": true, - "version": "2.21.0", + "version": "2.22.0", "npmClient": "yarn", "useNx": true } diff --git a/package.json b/package.json index 98347914e..7f9950ded 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jupyter-ai/monorepo", - "version": "2.21.0", + "version": "2.22.0", "description": "A generative AI extension for JupyterLab", "private": true, "keywords": [ diff --git a/packages/jupyter-ai-magics/package.json b/packages/jupyter-ai-magics/package.json index 0ca040120..c22c4e1cc 100644 --- a/packages/jupyter-ai-magics/package.json +++ b/packages/jupyter-ai-magics/package.json @@ -1,6 +1,6 @@ { "name": "@jupyter-ai/magics", - "version": "2.21.0", + "version": "2.22.0", "description": "Jupyter AI magics Python package. Not published on NPM.", "private": true, "homepage": "https://github.com/jupyterlab/jupyter-ai", diff --git a/packages/jupyter-ai-test/package.json b/packages/jupyter-ai-test/package.json index 84c1fd48c..1198570f1 100644 --- a/packages/jupyter-ai-test/package.json +++ b/packages/jupyter-ai-test/package.json @@ -1,6 +1,6 @@ { "name": "@jupyter-ai/test", - "version": "2.21.0", + "version": "2.22.0", "description": "Jupyter AI test package. Not published on NPM or PyPI.", "private": true, "homepage": "https://github.com/jupyterlab/jupyter-ai", diff --git a/packages/jupyter-ai/package.json b/packages/jupyter-ai/package.json index 56e36f401..70cde8552 100644 --- a/packages/jupyter-ai/package.json +++ b/packages/jupyter-ai/package.json @@ -1,6 +1,6 @@ { "name": "@jupyter-ai/core", - "version": "2.21.0", + "version": "2.22.0", "description": "A generative AI extension for JupyterLab", "keywords": [ "jupyter",