Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Adds a new "Favorited Branches" option to the branches visibility dropdown on the _Commit Graph_
- Adds _Add to Favorites_ or _Remove from Favorites_ context menu items to branches in the _Commit Graph_
- Adds _Add to Favorites_ or _Remove from Favorites_ context menu items to worktrees in the views
- Adds 👍 "Helpful" and 👎 "Unhelpful" feedback buttons to AI-generated Changelog ([#4449](https://github.com/gitkraken/vscode-gitlens/issues/4449))
- Adds ability to set or change the upstream branch for branches in the _Commit Graph_ and other GitLens views ([#4498](https://github.com/gitkraken/vscode-gitlens/issues/4498))
- Adds new _Set Upstream..._ and _Change Upstream..._ context menu items to branches in the _Commit Graph_ and other GitLens views
- Adds a new _upstream_ sub-command to the _branch_ Git Command Palette
Expand Down
20 changes: 20 additions & 0 deletions contributions.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@
"when": "resourceScheme == gitlens-ai-markdown && resource not in gitlens:tabs:ai:helpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && activeCustomEditorId == vscode.markdown.preview.editor",
"group": "navigation",
"order": 1
},
{
"when": "resourceScheme == untitled && resource in gitlens:tabs:ai:changelog && resource not in gitlens:tabs:ai:helpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && resourceLangId == markdown",
"group": "navigation",
"order": 1
}
]
}
Expand All @@ -165,6 +170,11 @@
"when": "resourceScheme == gitlens-ai-markdown && resource in gitlens:tabs:ai:helpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && activeCustomEditorId == vscode.markdown.preview.editor",
"group": "navigation",
"order": 1
},
{
"when": "resourceScheme == untitled && resource in gitlens:tabs:ai:changelog && resource in gitlens:tabs:ai:helpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && resourceLangId == markdown",
"group": "navigation",
"order": 1
}
]
}
Expand All @@ -178,6 +188,11 @@
"when": "resourceScheme == gitlens-ai-markdown && resource not in gitlens:tabs:ai:unhelpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && activeCustomEditorId == vscode.markdown.preview.editor",
"group": "navigation",
"order": 2
},
{
"when": "resourceScheme == untitled && resource in gitlens:tabs:ai:changelog && resource not in gitlens:tabs:ai:unhelpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && resourceLangId == markdown",
"group": "navigation",
"order": 2
}
]
}
Expand All @@ -191,6 +206,11 @@
"when": "resourceScheme == gitlens-ai-markdown && resource in gitlens:tabs:ai:unhelpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && activeCustomEditorId == vscode.markdown.preview.editor",
"group": "navigation",
"order": 2
},
{
"when": "resourceScheme == untitled && resource in gitlens:tabs:ai:changelog && resource in gitlens:tabs:ai:unhelpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && resourceLangId == markdown",
"group": "navigation",
"order": 1
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion docs/telemetry-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -1731,7 +1731,7 @@ void
'repoPrivacy': 'private' | 'public' | 'local',
'repository.visibility': 'private' | 'public' | 'local',
// Provided for compatibility with other GK surfaces
'source': 'account' | 'subscription' | 'graph' | 'patchDetails' | 'settings' | 'timeline' | 'home' | 'view' | 'code-suggest' | 'ai' | 'ai:markdown-preview' | 'ai:picker' | 'associateIssueWithBranch' | 'cloud-patches' | 'commandPalette' | 'deeplink' | 'editor:hover' | 'feature-badge' | 'feature-gate' | 'inspect' | 'inspect-overview' | 'integrations' | 'launchpad' | 'launchpad-indicator' | 'launchpad-view' | 'merge-target' | 'notification' | 'prompt' | 'quick-wizard' | 'rebaseEditor' | 'remoteProvider' | 'scm-input' | 'startWork' | 'trial-indicator' | 'walkthrough' | 'whatsnew' | 'worktrees'
'source': 'account' | 'subscription' | 'graph' | 'patchDetails' | 'settings' | 'timeline' | 'home' | 'view' | 'code-suggest' | 'ai' | 'ai:markdown-preview' | 'ai:markdown-editor' | 'ai:picker' | 'associateIssueWithBranch' | 'cloud-patches' | 'commandPalette' | 'deeplink' | 'editor:hover' | 'feature-badge' | 'feature-gate' | 'inspect' | 'inspect-overview' | 'integrations' | 'launchpad' | 'launchpad-indicator' | 'launchpad-view' | 'merge-target' | 'notification' | 'prompt' | 'quick-wizard' | 'rebaseEditor' | 'remoteProvider' | 'scm-input' | 'startWork' | 'trial-indicator' | 'walkthrough' | 'whatsnew' | 'worktrees'
}
```

Expand Down
88 changes: 54 additions & 34 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14192,15 +14192,20 @@
}
],
"editor/title": [
{
"command": "gitlens.graph.refresh",
"when": "activeWebviewPanelId === gitlens.graph",
"group": "navigation@-99"
},
{
"command": "gitlens.timeline.refresh",
"when": "activeWebviewPanelId === gitlens.timeline",
"group": "navigation@-99"
},
{
"submenu": "gitlens/graph/configuration",
"when": "activeWebviewPanelId === gitlens.graph",
"group": "navigation@-98"
"command": "gitlens.graph.split",
"when": "activeWebviewPanelId == gitlens.graph && resourceScheme == webview-panel && config.gitlens.graph.allowMultiple",
"group": "navigation@-97"
},
{
"command": "gitlens.timeline.split",
Expand All @@ -14217,37 +14222,6 @@
"when": "resource in gitlens:tabs:blameable && (gitlens:window:annotated == computing || resource in gitlens:tabs:annotated:computing) && config.gitlens.menus.editorGroup.blame",
"group": "navigation@100"
},
{
"command": "gitlens.diffWithPrevious",
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
"group": "navigation@97",
"alt": "gitlens.diffWithRevision"
},
{
"command": "gitlens.showQuickRevisionDetails",
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
"group": "navigation@98"
},
{
"command": "gitlens.diffWithNext",
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
"group": "navigation@99"
},
{
"command": "gitlens.diffWithWorking",
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
"group": "navigation@-99"
},
{
"command": "gitlens.graph.refresh",
"when": "activeWebviewPanelId === gitlens.graph",
"group": "navigation@-99"
},
{
"command": "gitlens.graph.split",
"when": "activeWebviewPanelId == gitlens.graph && resourceScheme == webview-panel && config.gitlens.graph.allowMultiple",
"group": "navigation@-97"
},
{
"command": "gitlens.toggleFileBlame",
"when": "resource in gitlens:tabs:blameable && resource not in gitlens:tabs:annotated && config.gitlens.menus.editorGroup.blame && config.gitlens.fileAnnotations.command == blame",
Expand All @@ -14271,16 +14245,42 @@
"when": "resource in gitlens:tabs:blameable && resource not in gitlens:tabs:annotated && !gitlens:window:annotated && config.gitlens.menus.editorGroup.blame && !config.gitlens.fileAnnotations.command",
"group": "navigation@100"
},
{
"command": "gitlens.diffWithPrevious",
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
"group": "navigation@97",
"alt": "gitlens.diffWithRevision"
},
{
"command": "gitlens.showQuickRevisionDetails",
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
"group": "navigation@98"
},
{
"command": "gitlens.diffWithNext",
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
"group": "navigation@99"
},
{
"command": "gitlens.openWorkingFile",
"when": "resourceScheme == git && gitlens:enabled && !isInDiffEditor",
"group": "navigation@-98"
},
{
"command": "gitlens.diffWithWorking",
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
"group": "navigation@-99"
},
{
"command": "gitlens.openWorkingFile",
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
"group": "navigation@-98"
},
{
"submenu": "gitlens/graph/configuration",
"when": "activeWebviewPanelId === gitlens.graph",
"group": "navigation@-98"
},
{
"command": "gitlens.openRevisionFile",
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled && isInDiffEditor",
Expand Down Expand Up @@ -14311,6 +14311,26 @@
"when": "resourceScheme == gitlens-ai-markdown && activeCustomEditorId == vscode.markdown.preview.editor && resourcePath =~ /^\\/explain\\//",
"group": "navigation@3"
},
{
"command": "gitlens.ai.feedback.helpful",
"when": "resourceScheme == untitled && resource in gitlens:tabs:ai:changelog && resource not in gitlens:tabs:ai:helpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && resourceLangId == markdown",
"group": "navigation@1"
},
{
"command": "gitlens.ai.feedback.helpful.chosen",
"when": "resourceScheme == untitled && resource in gitlens:tabs:ai:changelog && resource in gitlens:tabs:ai:helpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && resourceLangId == markdown",
"group": "navigation@1"
},
{
"command": "gitlens.ai.feedback.unhelpful.chosen",
"when": "resourceScheme == untitled && resource in gitlens:tabs:ai:changelog && resource in gitlens:tabs:ai:unhelpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && resourceLangId == markdown",
"group": "navigation@1"
},
{
"command": "gitlens.ai.feedback.unhelpful",
"when": "resourceScheme == untitled && resource in gitlens:tabs:ai:changelog && resource not in gitlens:tabs:ai:unhelpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && resourceLangId == markdown",
"group": "navigation@2"
},
{
"command": "gitlens.openPatch",
"when": "false && editorLangId == diff"
Expand Down
64 changes: 61 additions & 3 deletions src/commands/generateChangelog.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import type { CancellationToken, ProgressOptions } from 'vscode';
import type { CancellationToken, ProgressOptions, Uri } from 'vscode';
import { ProgressLocation, window, workspace } from 'vscode';
import type { Source } from '../constants.telemetry';
import type { Container } from '../container';
import type { GitReference } from '../git/models/reference';
import { getChangesForChangelog } from '../git/utils/-webview/log.utils';
import { createRevisionRange, shortenRevision } from '../git/utils/revision.utils';
import { showGenericErrorMessage } from '../messages';
import type { AIGenerateChangelogChanges } from '../plus/ai/aiProviderService';
import type { AIGenerateChangelogChanges, AIResultContext } from '../plus/ai/aiProviderService';
import { getAIResultContext } from '../plus/ai/utils/-webview/ai.utils';
import { showComparisonPicker } from '../quickpicks/comparisonPicker';
import { command } from '../system/-webview/command';
import { setContext } from '../system/-webview/context';
import type { Deferrable } from '../system/function/debounce';
import { debounce } from '../system/function/debounce';
import type { Lazy } from '../system/lazy';
import { lazy } from '../system/lazy';
import { Logger } from '../system/logger';
Expand All @@ -21,6 +25,36 @@ export interface GenerateChangelogCommandArgs {
source?: Source;
}

// Storage for AI feedback context associated with changelog documents
const changelogFeedbackContexts = new Map<string, AIResultContext>();
export function getChangelogFeedbackContext(documentUri: string): AIResultContext | undefined {
return changelogFeedbackContexts.get(documentUri);
}
function setChangelogFeedbackContext(documentUri: string, context: AIResultContext): void {
changelogFeedbackContexts.set(documentUri, context);
}
function clearChangelogFeedbackContext(documentUri: string): void {
changelogFeedbackContexts.delete(documentUri);
}

// Storage for changelog document URIs
const changelogUris = new Set<Uri>();
let _updateChangelogContextDebounced: Deferrable<() => void> | undefined;
function updateChangelogContext(): void {
_updateChangelogContextDebounced ??= debounce(() => {
void setContext('gitlens:tabs:ai:changelog', [...changelogUris]);
}, 100);
_updateChangelogContextDebounced();
}
function addChangelogUri(uri: Uri): void {
changelogUris.add(uri);
updateChangelogContext();
}
function removeChangelogUri(uri: Uri): void {
changelogUris.delete(uri);
updateChangelogContext();
}

@command()
export class GenerateChangelogCommand extends GlCommandBase {
constructor(private readonly container: Container) {
Expand Down Expand Up @@ -98,12 +132,22 @@ export async function generateChangelogAndOpenMarkdownDocument(
if (result === 'cancelled') return;

const { range, changes: { length: count } = [] } = await changes.value;
const feedbackContext = result && getAIResultContext(result);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this always guaranteed to hit the case uri?.scheme === Schemes.GitLensAIMarkdown? Otherwise on a quick read it looks like it would result in a getChangelogFeedbackContext which wouldn't exist yet (I must be missing something).

Copy link
Member Author

Choose a reason for hiding this comment

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

@axosoft-ramint
It is rather guaranteed that changelog's scheme is not GitLensAIMarkdown. It's "untitled".

  • getAIResultContext is an util function creates an object assigned to feedbackContext.
  • const document = await workspace.openTextDocument creates a document with untitled scheme.
  • then setChangelogFeedbackContext stores the context.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks


let content = `# Changelog for ${range.head.label ?? range.head.ref}\n`;
if (result != null) {
content += `> Generated by ${result.model.name} from ${pluralize('commit', count)} between ${
range.head.label ?? range.head.ref
} and ${range.base.label ?? range.base.ref}\n\n----\n\n${result.content}\n`;
} and ${range.base.label ?? range.base.ref}\n`;

// Add feedback note if telemetry is enabled
if (feedbackContext && container.telemetry.enabled) {
content += '\n\n';
content += 'Use the 👍 and 👎 buttons in the editor toolbar to provide feedback on this AI response. ';
content += '*Your feedback helps us improve our AI features.*';
}

content += `\n\n----\n\n${result.content}\n`;
} else {
content += `> No changes found between ${range.head.label ?? range.head.ref} and ${
range.base.label ?? range.base.ref
Expand All @@ -112,5 +156,19 @@ export async function generateChangelogAndOpenMarkdownDocument(

// open an untitled editor
const document = await workspace.openTextDocument({ language: 'markdown', content: content });
if (feedbackContext) {
// Store feedback context for this document
setChangelogFeedbackContext(document.uri.toString(), feedbackContext);
// Add to changelog URIs context even for no-results documents
addChangelogUri(document.uri);
// Clean up context when document is closed
const disposable = workspace.onDidCloseTextDocument(closedDoc => {
if (closedDoc.uri.toString() === document.uri.toString()) {
clearChangelogFeedbackContext(document.uri.toString());
removeChangelogUri(document.uri);
disposable.dispose();
}
});
}
await window.showTextDocument(document);
}
1 change: 1 addition & 0 deletions src/constants.context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type ContextKeys = {
'gitlens:schemes:trackable': string[];
'gitlens:tabs:ai:helpful': Uri[];
'gitlens:tabs:ai:unhelpful': Uri[];
'gitlens:tabs:ai:changelog': Uri[];
'gitlens:tabs:annotated': Uri[];
'gitlens:tabs:annotated:computing': Uri[];
'gitlens:tabs:blameable': Uri[];
Expand Down
1 change: 1 addition & 0 deletions src/constants.telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,7 @@ export type Sources =
| 'account'
| 'ai'
| 'ai:markdown-preview'
| 'ai:markdown-editor'
| 'ai:picker'
| 'associateIssueWithBranch'
| 'cloud-patches'
Expand Down
36 changes: 25 additions & 11 deletions src/plus/ai/utils/-webview/ai.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Disposable, QuickInputButton } from 'vscode';
import { env, ThemeIcon, Uri, window } from 'vscode';
import { getMarkdownDocument } from '../../../../commands/aiFeedback';
import { getChangelogFeedbackContext } from '../../../../commands/generateChangelog';
import { Schemes } from '../../../../constants';
import type { AIProviders } from '../../../../constants.ai';
import type { Container } from '../../../../container';
Expand Down Expand Up @@ -284,17 +285,30 @@ export function getAIResultContext(result: AIResult): AIResultContext {
}

export function extractAIResultContext(uri: Uri | undefined): AIResultContext | undefined {
if (uri?.scheme !== Schemes.GitLensAIMarkdown) return undefined;

const { authority } = uri;
if (!authority) return undefined;
if (uri?.scheme === Schemes.GitLensAIMarkdown) {
const { authority } = uri;
if (!authority) return undefined;

try {
const context: AIResultContext | undefined = getMarkdownDocument(uri.toString());
if (context) return context;

const metadata = decodeGitLensRevisionUriAuthority<MarkdownContentMetadata>(authority);
return metadata.context;
} catch (ex) {
Logger.error(ex, 'extractResultContext');
return undefined;
}
}

try {
const context: AIResultContext | undefined = getMarkdownDocument(uri.toString());
const metadata = decodeGitLensRevisionUriAuthority<MarkdownContentMetadata>(authority);
return context ?? metadata.context;
} catch (ex) {
Logger.error(ex, 'extractResultContext');
return undefined;
// Check for untitled documents with stored changelog feedback context
if (uri?.scheme === 'untitled') {
try {
return getChangelogFeedbackContext(uri.toString());
} catch {
return undefined;
}
}

return undefined;
}