Skip to content

Commit 489bb32

Browse files
committed
Adds AI feedback support for changelog markdown editors
Enables the use of helpful/unhelpful AI feedback buttons in untitled markdown editors generated by the changelog command. Tracks feedback context for these documents, updates relevant context keys, and ensures telemetry compatibility. Also improves feedback instructions in generated changelogs to encourage user interaction. Updates telemetry and menu configuration to support this new AI feedback capability for changelog editors. (#4449, #4479)
1 parent 7d3072c commit 489bb32

File tree

7 files changed

+163
-49
lines changed

7 files changed

+163
-49
lines changed

contributions.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@
152152
"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",
153153
"group": "navigation",
154154
"order": 1
155+
},
156+
{
157+
"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",
158+
"group": "navigation",
159+
"order": 1
155160
}
156161
]
157162
}
@@ -165,6 +170,11 @@
165170
"when": "resourceScheme == gitlens-ai-markdown && resource in gitlens:tabs:ai:helpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && activeCustomEditorId == vscode.markdown.preview.editor",
166171
"group": "navigation",
167172
"order": 1
173+
},
174+
{
175+
"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",
176+
"group": "navigation",
177+
"order": 1
168178
}
169179
]
170180
}
@@ -178,6 +188,11 @@
178188
"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",
179189
"group": "navigation",
180190
"order": 2
191+
},
192+
{
193+
"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",
194+
"group": "navigation",
195+
"order": 2
181196
}
182197
]
183198
}
@@ -191,6 +206,11 @@
191206
"when": "resourceScheme == gitlens-ai-markdown && resource in gitlens:tabs:ai:unhelpful && config.gitlens.telemetry.enabled && config.telemetry.telemetryLevel != off && activeCustomEditorId == vscode.markdown.preview.editor",
192207
"group": "navigation",
193208
"order": 2
209+
},
210+
{
211+
"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",
212+
"group": "navigation",
213+
"order": 1
194214
}
195215
]
196216
}

docs/telemetry-events.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1731,7 +1731,7 @@ void
17311731
'repoPrivacy': 'private' | 'public' | 'local',
17321732
'repository.visibility': 'private' | 'public' | 'local',
17331733
// Provided for compatibility with other GK surfaces
1734-
'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'
1734+
'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'
17351735
}
17361736
```
17371737

package.json

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14192,15 +14192,20 @@
1419214192
}
1419314193
],
1419414194
"editor/title": [
14195+
{
14196+
"command": "gitlens.graph.refresh",
14197+
"when": "activeWebviewPanelId === gitlens.graph",
14198+
"group": "navigation@-99"
14199+
},
1419514200
{
1419614201
"command": "gitlens.timeline.refresh",
1419714202
"when": "activeWebviewPanelId === gitlens.timeline",
1419814203
"group": "navigation@-99"
1419914204
},
1420014205
{
14201-
"submenu": "gitlens/graph/configuration",
14202-
"when": "activeWebviewPanelId === gitlens.graph",
14203-
"group": "navigation@-98"
14206+
"command": "gitlens.graph.split",
14207+
"when": "activeWebviewPanelId == gitlens.graph && resourceScheme == webview-panel && config.gitlens.graph.allowMultiple",
14208+
"group": "navigation@-97"
1420414209
},
1420514210
{
1420614211
"command": "gitlens.timeline.split",
@@ -14217,37 +14222,6 @@
1421714222
"when": "resource in gitlens:tabs:blameable && (gitlens:window:annotated == computing || resource in gitlens:tabs:annotated:computing) && config.gitlens.menus.editorGroup.blame",
1421814223
"group": "navigation@100"
1421914224
},
14220-
{
14221-
"command": "gitlens.diffWithPrevious",
14222-
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14223-
"group": "navigation@97",
14224-
"alt": "gitlens.diffWithRevision"
14225-
},
14226-
{
14227-
"command": "gitlens.showQuickRevisionDetails",
14228-
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14229-
"group": "navigation@98"
14230-
},
14231-
{
14232-
"command": "gitlens.diffWithNext",
14233-
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14234-
"group": "navigation@99"
14235-
},
14236-
{
14237-
"command": "gitlens.diffWithWorking",
14238-
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
14239-
"group": "navigation@-99"
14240-
},
14241-
{
14242-
"command": "gitlens.graph.refresh",
14243-
"when": "activeWebviewPanelId === gitlens.graph",
14244-
"group": "navigation@-99"
14245-
},
14246-
{
14247-
"command": "gitlens.graph.split",
14248-
"when": "activeWebviewPanelId == gitlens.graph && resourceScheme == webview-panel && config.gitlens.graph.allowMultiple",
14249-
"group": "navigation@-97"
14250-
},
1425114225
{
1425214226
"command": "gitlens.toggleFileBlame",
1425314227
"when": "resource in gitlens:tabs:blameable && resource not in gitlens:tabs:annotated && config.gitlens.menus.editorGroup.blame && config.gitlens.fileAnnotations.command == blame",
@@ -14271,16 +14245,42 @@
1427114245
"when": "resource in gitlens:tabs:blameable && resource not in gitlens:tabs:annotated && !gitlens:window:annotated && config.gitlens.menus.editorGroup.blame && !config.gitlens.fileAnnotations.command",
1427214246
"group": "navigation@100"
1427314247
},
14248+
{
14249+
"command": "gitlens.diffWithPrevious",
14250+
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14251+
"group": "navigation@97",
14252+
"alt": "gitlens.diffWithRevision"
14253+
},
14254+
{
14255+
"command": "gitlens.showQuickRevisionDetails",
14256+
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14257+
"group": "navigation@98"
14258+
},
14259+
{
14260+
"command": "gitlens.diffWithNext",
14261+
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14262+
"group": "navigation@99"
14263+
},
1427414264
{
1427514265
"command": "gitlens.openWorkingFile",
1427614266
"when": "resourceScheme == git && gitlens:enabled && !isInDiffEditor",
1427714267
"group": "navigation@-98"
1427814268
},
14269+
{
14270+
"command": "gitlens.diffWithWorking",
14271+
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
14272+
"group": "navigation@-99"
14273+
},
1427914274
{
1428014275
"command": "gitlens.openWorkingFile",
1428114276
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
1428214277
"group": "navigation@-98"
1428314278
},
14279+
{
14280+
"submenu": "gitlens/graph/configuration",
14281+
"when": "activeWebviewPanelId === gitlens.graph",
14282+
"group": "navigation@-98"
14283+
},
1428414284
{
1428514285
"command": "gitlens.openRevisionFile",
1428614286
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled && isInDiffEditor",
@@ -14311,6 +14311,26 @@
1431114311
"when": "resourceScheme == gitlens-ai-markdown && activeCustomEditorId == vscode.markdown.preview.editor && resourcePath =~ /^\\/explain\\//",
1431214312
"group": "navigation@3"
1431314313
},
14314+
{
14315+
"command": "gitlens.ai.feedback.helpful",
14316+
"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",
14317+
"group": "navigation@1"
14318+
},
14319+
{
14320+
"command": "gitlens.ai.feedback.helpful.chosen",
14321+
"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",
14322+
"group": "navigation@1"
14323+
},
14324+
{
14325+
"command": "gitlens.ai.feedback.unhelpful.chosen",
14326+
"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",
14327+
"group": "navigation@1"
14328+
},
14329+
{
14330+
"command": "gitlens.ai.feedback.unhelpful",
14331+
"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",
14332+
"group": "navigation@2"
14333+
},
1431414334
{
1431514335
"command": "gitlens.openPatch",
1431614336
"when": "false && editorLangId == diff"

src/commands/generateChangelog.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
import type { CancellationToken, ProgressOptions } from 'vscode';
1+
import type { CancellationToken, ProgressOptions, Uri } from 'vscode';
22
import { ProgressLocation, window, workspace } from 'vscode';
33
import type { Source } from '../constants.telemetry';
44
import type { Container } from '../container';
55
import type { GitReference } from '../git/models/reference';
66
import { getChangesForChangelog } from '../git/utils/-webview/log.utils';
77
import { createRevisionRange, shortenRevision } from '../git/utils/revision.utils';
88
import { showGenericErrorMessage } from '../messages';
9-
import type { AIGenerateChangelogChanges } from '../plus/ai/aiProviderService';
9+
import type { AIGenerateChangelogChanges, AIResultContext } from '../plus/ai/aiProviderService';
10+
import { getAIResultContext } from '../plus/ai/utils/-webview/ai.utils';
1011
import { showComparisonPicker } from '../quickpicks/comparisonPicker';
1112
import { command } from '../system/-webview/command';
13+
import { setContext } from '../system/-webview/context';
14+
import type { Deferrable } from '../system/function/debounce';
15+
import { debounce } from '../system/function/debounce';
1216
import type { Lazy } from '../system/lazy';
1317
import { lazy } from '../system/lazy';
1418
import { Logger } from '../system/logger';
@@ -21,6 +25,36 @@ export interface GenerateChangelogCommandArgs {
2125
source?: Source;
2226
}
2327

28+
// Storage for AI feedback context associated with changelog documents
29+
const changelogFeedbackContexts = new Map<string, AIResultContext>();
30+
export function getChangelogFeedbackContext(documentUri: string): AIResultContext | undefined {
31+
return changelogFeedbackContexts.get(documentUri);
32+
}
33+
function setChangelogFeedbackContext(documentUri: string, context: AIResultContext): void {
34+
changelogFeedbackContexts.set(documentUri, context);
35+
}
36+
function clearChangelogFeedbackContext(documentUri: string): void {
37+
changelogFeedbackContexts.delete(documentUri);
38+
}
39+
40+
// Storage for changelog document URIs
41+
const changelogUris = new Set<Uri>();
42+
let _updateChangelogContextDebounced: Deferrable<() => void> | undefined;
43+
function updateChangelogContext(): void {
44+
_updateChangelogContextDebounced ??= debounce(() => {
45+
void setContext('gitlens:tabs:ai:changelog', [...changelogUris]);
46+
}, 100);
47+
_updateChangelogContextDebounced();
48+
}
49+
function addChangelogUri(uri: Uri): void {
50+
changelogUris.add(uri);
51+
updateChangelogContext();
52+
}
53+
function removeChangelogUri(uri: Uri): void {
54+
changelogUris.delete(uri);
55+
updateChangelogContext();
56+
}
57+
2458
@command()
2559
export class GenerateChangelogCommand extends GlCommandBase {
2660
constructor(private readonly container: Container) {
@@ -98,12 +132,22 @@ export async function generateChangelogAndOpenMarkdownDocument(
98132
if (result === 'cancelled') return;
99133

100134
const { range, changes: { length: count } = [] } = await changes.value;
135+
const feedbackContext = result && getAIResultContext(result);
101136

102137
let content = `# Changelog for ${range.head.label ?? range.head.ref}\n`;
103138
if (result != null) {
104139
content += `> Generated by ${result.model.name} from ${pluralize('commit', count)} between ${
105140
range.head.label ?? range.head.ref
106-
} and ${range.base.label ?? range.base.ref}\n\n----\n\n${result.content}\n`;
141+
} and ${range.base.label ?? range.base.ref}\n`;
142+
143+
// Add feedback note if telemetry is enabled
144+
if (feedbackContext && container.telemetry.enabled) {
145+
content += '\n\n';
146+
content += 'Use the 👍 and 👎 buttons in the editor toolbar to provide feedback on this AI response. ';
147+
content += '*Your feedback helps us improve our AI features.*';
148+
}
149+
150+
content += `\n\n----\n\n${result.content}\n`;
107151
} else {
108152
content += `> No changes found between ${range.head.label ?? range.head.ref} and ${
109153
range.base.label ?? range.base.ref
@@ -112,5 +156,19 @@ export async function generateChangelogAndOpenMarkdownDocument(
112156

113157
// open an untitled editor
114158
const document = await workspace.openTextDocument({ language: 'markdown', content: content });
159+
if (feedbackContext) {
160+
// Store feedback context for this document
161+
setChangelogFeedbackContext(document.uri.toString(), feedbackContext);
162+
// Add to changelog URIs context even for no-results documents
163+
addChangelogUri(document.uri);
164+
// Clean up context when document is closed
165+
const disposable = workspace.onDidCloseTextDocument(closedDoc => {
166+
if (closedDoc.uri.toString() === document.uri.toString()) {
167+
clearChangelogFeedbackContext(document.uri.toString());
168+
removeChangelogUri(document.uri);
169+
disposable.dispose();
170+
}
171+
});
172+
}
115173
await window.showTextDocument(document);
116174
}

src/constants.context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type ContextKeys = {
3939
'gitlens:schemes:trackable': string[];
4040
'gitlens:tabs:ai:helpful': Uri[];
4141
'gitlens:tabs:ai:unhelpful': Uri[];
42+
'gitlens:tabs:ai:changelog': Uri[];
4243
'gitlens:tabs:annotated': Uri[];
4344
'gitlens:tabs:annotated:computing': Uri[];
4445
'gitlens:tabs:blameable': Uri[];

src/constants.telemetry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,7 @@ export type Sources =
10371037
| 'account'
10381038
| 'ai'
10391039
| 'ai:markdown-preview'
1040+
| 'ai:markdown-editor'
10401041
| 'ai:picker'
10411042
| 'associateIssueWithBranch'
10421043
| 'cloud-patches'

src/plus/ai/utils/-webview/ai.utils.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Disposable, QuickInputButton } from 'vscode';
22
import { env, ThemeIcon, Uri, window } from 'vscode';
33
import { getMarkdownDocument } from '../../../../commands/aiFeedback';
4+
import { getChangelogFeedbackContext } from '../../../../commands/generateChangelog';
45
import { Schemes } from '../../../../constants';
56
import type { AIProviders } from '../../../../constants.ai';
67
import type { Container } from '../../../../container';
@@ -284,17 +285,30 @@ export function getAIResultContext(result: AIResult): AIResultContext {
284285
}
285286

286287
export function extractAIResultContext(uri: Uri | undefined): AIResultContext | undefined {
287-
if (uri?.scheme !== Schemes.GitLensAIMarkdown) return undefined;
288-
289-
const { authority } = uri;
290-
if (!authority) return undefined;
288+
if (uri?.scheme === Schemes.GitLensAIMarkdown) {
289+
const { authority } = uri;
290+
if (!authority) return undefined;
291+
292+
try {
293+
const context: AIResultContext | undefined = getMarkdownDocument(uri.toString());
294+
if (context) return context;
295+
296+
const metadata = decodeGitLensRevisionUriAuthority<MarkdownContentMetadata>(authority);
297+
return metadata.context;
298+
} catch (ex) {
299+
Logger.error(ex, 'extractResultContext');
300+
return undefined;
301+
}
302+
}
291303

292-
try {
293-
const context: AIResultContext | undefined = getMarkdownDocument(uri.toString());
294-
const metadata = decodeGitLensRevisionUriAuthority<MarkdownContentMetadata>(authority);
295-
return context ?? metadata.context;
296-
} catch (ex) {
297-
Logger.error(ex, 'extractResultContext');
298-
return undefined;
304+
// Check for untitled documents with stored changelog feedback context
305+
if (uri?.scheme === 'untitled') {
306+
try {
307+
return getChangelogFeedbackContext(uri.toString());
308+
} catch {
309+
return undefined;
310+
}
299311
}
312+
313+
return undefined;
300314
}

0 commit comments

Comments
 (0)