Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
14 changes: 12 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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": [
Expand Down
66 changes: 66 additions & 0 deletions src/commands/summarizationCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,72 @@ export class SummarizationCommands {
}
}

/**
* Handle summarizing selected text and inserting summary below the selection
*/
public async handleSummarizeSelection(): Promise<void> {
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 (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);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current logic for inserting the summary is a bit complex and can lead to inconsistent vertical spacing depending on whether the selection ends at the end of a line or mid-line. This can be simplified to a single, consistent behavior by always inserting the summary at the end of the line where the selection terminates. This makes the code easier to read and ensures predictable output.

                    // 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 => {
                        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
// ========================================================================
Expand Down
6 changes: 5 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down Expand Up @@ -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,
Expand Down
48 changes: 48 additions & 0 deletions src/services/summarizationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,4 +526,52 @@ ${note.content}
}
return 'GitHub Copilot';
}

/**
* Summarize selected text (concise 2-3 sentence summary)
*/
async summarizeSelection(selectedText: string): Promise<string> {
// 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)}`);
}
}
}