From aef7301747809902ad6ead8afae0ecd76bc36d73 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Dec 2025 16:56:46 +0000 Subject: [PATCH 1/4] feat: add Summarize Selection context menu command - Add new context menu item in Noted submenu for summarizing selected text - Works in any file (not just Notes folder) - Uses VS Code LLM API (GitHub Copilot) for AI-powered summarization - Generates concise 2-3 sentence summary - Inserts summary below selection with "## Summary of Selection" header - Shows error message if Copilot is unavailable --- package.json | 14 +++++- src/commands/summarizationCommands.ts | 66 +++++++++++++++++++++++++++ src/extension.ts | 6 ++- src/services/summarizationService.ts | 48 +++++++++++++++++++ 4 files changed, 131 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 4b481f1..8074129 100644 --- a/package.json +++ b/package.json @@ -681,6 +681,11 @@ "title": "Generate Tags for Current Note", "icon": "$(sparkle)" }, + { + "command": "noted.summarizeSelection", + "title": "Summarize Selection", + "icon": "$(sparkle)" + }, { "command": "noted.batchGenerateTags", "title": "Noted: Batch Generate Tags", @@ -806,15 +811,20 @@ "when": "editorHasSelection", "group": "1_edit@1" }, + { + "command": "noted.summarizeSelection", + "when": "editorHasSelection", + "group": "2_ai@1" + }, { "command": "noted.summarizeCurrentNote", "when": "resourcePath =~ /Notes.*\\.(txt|md)$/", - "group": "2_ai@1" + "group": "2_ai@2" }, { "command": "noted.generateTagsCurrentNote", "when": "resourcePath =~ /Notes.*\\.(txt|md)$/", - "group": "2_ai@2" + "group": "2_ai@3" } ], "editor/context": [ diff --git a/src/commands/summarizationCommands.ts b/src/commands/summarizationCommands.ts index 03e70dc..c1dcf1b 100644 --- a/src/commands/summarizationCommands.ts +++ b/src/commands/summarizationCommands.ts @@ -341,6 +341,72 @@ export class SummarizationCommands { } } + /** + * Handle summarizing selected text and inserting summary below the selection + */ + public async handleSummarizeSelection(): Promise { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage('No editor is currently open'); + return; + } + + const selection = editor.selection; + if (selection.isEmpty) { + vscode.window.showErrorMessage('No text is selected. Please select some text to summarize.'); + return; + } + + const selectedText = editor.document.getText(selection); + if (!selectedText.trim()) { + vscode.window.showErrorMessage('Selected text is empty or contains only whitespace'); + return; + } + + try { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: 'Summarizing selection...', + cancellable: true + }, + async (progress, token) => { + if (token.isCancellationRequested) { + return; + } + + const summary = await this.summarizationService.summarizeSelection(selectedText); + + if (token.isCancellationRequested) { + return; + } + + // Insert summary below the selection + const endPosition = selection.end; + const insertPosition = new vscode.Position(endPosition.line + 1, 0); + + // Format the summary with header + const summaryText = `\n## Summary of Selection\n\n${summary}\n`; + + await editor.edit(editBuilder => { + // If we're at the end of a line, add a newline first + const lineText = editor.document.lineAt(endPosition.line).text; + if (endPosition.character === lineText.length) { + editBuilder.insert(new vscode.Position(endPosition.line, lineText.length), summaryText); + } else { + // Insert at the beginning of the next line + editBuilder.insert(insertPosition, summaryText); + } + }); + + vscode.window.showInformationMessage('Summary inserted below selection'); + } + ); + } catch (error) { + vscode.window.showErrorMessage(`Failed to generate summary: ${error instanceof Error ? error.message : String(error)}`); + } + } + // ======================================================================== // Private Helper Methods // ======================================================================== diff --git a/src/extension.ts b/src/extension.ts index e50d8c4..fecbf67 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2081,6 +2081,10 @@ export function activate(context: vscode.ExtensionContext) { await summarizationCommands.handleSummarizeCurrentNote(); }); + let summarizeSelection = vscode.commands.registerCommand('noted.summarizeSelection', async () => { + await summarizationCommands.handleSummarizeSelection(); + }); + let summarizeRecent = vscode.commands.registerCommand('noted.summarizeRecent', async () => { await summarizationCommands.handleSummarizeRecent(); }); @@ -2226,7 +2230,7 @@ export function activate(context: vscode.ExtensionContext) { showPreview, showMarkdownToolbar, undoCommand, redoCommand, showUndoHistory, clearUndoHistory, renameSymbol, - summarizeNote, summarizeCurrentNote, summarizeRecent, summarizeWeek, summarizeMonth, summarizeCustomRange, clearSummaryCache, summarizeSearchResults, + summarizeNote, summarizeCurrentNote, summarizeSelection, summarizeRecent, summarizeWeek, summarizeMonth, summarizeCustomRange, clearSummaryCache, summarizeSearchResults, showSummaryHistory, compareSummaries, restoreSummaryVersion, clearSummaryHistory, showSummaryHistoryStats, createPromptTemplate, editPromptTemplate, deletePromptTemplate, duplicatePromptTemplate, listPromptTemplates, viewTemplateVariables, suggestTags, diff --git a/src/services/summarizationService.ts b/src/services/summarizationService.ts index ba43067..01ad184 100644 --- a/src/services/summarizationService.ts +++ b/src/services/summarizationService.ts @@ -526,4 +526,52 @@ ${note.content} } return 'GitHub Copilot'; } + + /** + * Summarize selected text (concise 2-3 sentence summary) + */ + async summarizeSelection(selectedText: string): Promise { + // Check if AI is enabled + if (!this.isAIEnabled()) { + throw new Error('AI summarization is disabled. Enable it in settings: noted.ai.enabled'); + } + + // Check if Language Model API is available + if (!(await this.isLanguageModelAvailable())) { + throw new Error('Copilot is not available. Please install and enable GitHub Copilot to use AI summarization features.'); + } + + // Truncate if too large + const maxChars = 16000; + const truncatedText = selectedText.length > maxChars + ? selectedText.substring(0, maxChars) + '\n\n[Text truncated for summarization]' + : selectedText; + + // Build a concise summarization prompt + const prompt = `Summarize the following text in 2-3 concise sentences, capturing the main points: + +--- +${truncatedText} +--- + +Provide only the summary without any preamble or extra formatting.`; + + // Call Language Model API + try { + const model = await selectAIModel(); + const messages = [vscode.LanguageModelChatMessage.User(prompt)]; + + const response = await model.sendRequest(messages, {}, new vscode.CancellationTokenSource().token); + + let summary = ''; + for await (const chunk of response.text) { + summary += chunk; + } + + return summary.trim(); + + } catch (error) { + throw new Error(`Failed to generate summary: ${error instanceof Error ? error.message : String(error)}`); + } + } } From a157dd4f05152a12eb8e778d69079338bcc60a6b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Dec 2025 17:00:51 +0000 Subject: [PATCH 2/4] fix: add empty line before Summary of Selection header --- src/commands/summarizationCommands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/summarizationCommands.ts b/src/commands/summarizationCommands.ts index c1dcf1b..21a5069 100644 --- a/src/commands/summarizationCommands.ts +++ b/src/commands/summarizationCommands.ts @@ -385,8 +385,8 @@ export class SummarizationCommands { const endPosition = selection.end; const insertPosition = new vscode.Position(endPosition.line + 1, 0); - // Format the summary with header - const summaryText = `\n## Summary of Selection\n\n${summary}\n`; + // Format the summary with header (extra blank line before header) + const summaryText = `\n\n## Summary of Selection\n\n${summary}\n`; await editor.edit(editBuilder => { // If we're at the end of a line, add a newline first From 160d840d8647d42ac4012d3e15b3cd7c6e1ec179 Mon Sep 17 00:00:00 2001 From: Jason Rueckert Date: Thu, 11 Dec 2025 09:08:12 -0800 Subject: [PATCH 3/4] Update src/commands/summarizationCommands.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/commands/summarizationCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/summarizationCommands.ts b/src/commands/summarizationCommands.ts index 21a5069..f031ba5 100644 --- a/src/commands/summarizationCommands.ts +++ b/src/commands/summarizationCommands.ts @@ -375,7 +375,7 @@ export class SummarizationCommands { return; } - const summary = await this.summarizationService.summarizeSelection(selectedText); + const summary = await this.summarizationService.summarizeSelection(selectedText, token); if (token.isCancellationRequested) { return; From 29a776bf4515962ddb4ca859f5906afacea20451 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Dec 2025 17:11:57 +0000 Subject: [PATCH 4/4] fix: pass cancellation token to summarizeSelection and simplify insertion - Add cancellation token parameter to summarizeSelection method - Pass token from withProgress to enable proper cancellation - Simplify insertion logic to always insert at end of selection line --- src/commands/summarizationCommands.ts | 16 +++++----------- src/services/summarizationService.ts | 12 +++--------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/commands/summarizationCommands.ts b/src/commands/summarizationCommands.ts index f031ba5..53bf4de 100644 --- a/src/commands/summarizationCommands.ts +++ b/src/commands/summarizationCommands.ts @@ -381,22 +381,16 @@ export class SummarizationCommands { return; } - // Insert summary below the selection - const endPosition = selection.end; - const insertPosition = new vscode.Position(endPosition.line + 1, 0); + // Insert summary at the end of the line containing the selection's end point + // to ensure consistent spacing. + const endLine = editor.document.lineAt(selection.end.line); + const insertPosition = endLine.range.end; // Format the summary with header (extra blank line before header) const summaryText = `\n\n## Summary of Selection\n\n${summary}\n`; await editor.edit(editBuilder => { - // If we're at the end of a line, add a newline first - const lineText = editor.document.lineAt(endPosition.line).text; - if (endPosition.character === lineText.length) { - editBuilder.insert(new vscode.Position(endPosition.line, lineText.length), summaryText); - } else { - // Insert at the beginning of the next line - editBuilder.insert(insertPosition, summaryText); - } + editBuilder.insert(insertPosition, summaryText); }); vscode.window.showInformationMessage('Summary inserted below selection'); diff --git a/src/services/summarizationService.ts b/src/services/summarizationService.ts index 01ad184..e581646 100644 --- a/src/services/summarizationService.ts +++ b/src/services/summarizationService.ts @@ -530,7 +530,7 @@ ${note.content} /** * Summarize selected text (concise 2-3 sentence summary) */ - async summarizeSelection(selectedText: string): Promise { + async summarizeSelection(selectedText: string, token: vscode.CancellationToken): Promise { // Check if AI is enabled if (!this.isAIEnabled()) { throw new Error('AI summarization is disabled. Enable it in settings: noted.ai.enabled'); @@ -548,20 +548,14 @@ ${note.content} : selectedText; // Build a concise summarization prompt - const prompt = `Summarize the following text in 2-3 concise sentences, capturing the main points: - ---- -${truncatedText} ---- - -Provide only the summary without any preamble or extra formatting.`; + const prompt = `Summarize the following text in 2-3 concise sentences, capturing the main points:\n\n---\n${truncatedText}\n---\n\nProvide only the summary without any preamble or extra formatting.`; // Call Language Model API try { const model = await selectAIModel(); const messages = [vscode.LanguageModelChatMessage.User(prompt)]; - const response = await model.sendRequest(messages, {}, new vscode.CancellationTokenSource().token); + const response = await model.sendRequest(messages, {}, token); let summary = ''; for await (const chunk of response.text) {