Skip to content

Commit 8b7c748

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)
1 parent 755ef07 commit 8b7c748

File tree

7 files changed

+160
-48
lines changed

7 files changed

+160
-48
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
@@ -1730,7 +1730,7 @@ void
17301730
'repoPrivacy': 'private' | 'public' | 'local',
17311731
'repository.visibility': 'private' | 'public' | 'local',
17321732
// Provided for compatibility with other GK surfaces
1733-
'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'
1733+
'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'
17341734
}
17351735
```
17361736

package.json

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14055,15 +14055,20 @@
1405514055
}
1405614056
],
1405714057
"editor/title": [
14058+
{
14059+
"command": "gitlens.graph.refresh",
14060+
"when": "activeWebviewPanelId === gitlens.graph",
14061+
"group": "navigation@-99"
14062+
},
1405814063
{
1405914064
"command": "gitlens.timeline.refresh",
1406014065
"when": "activeWebviewPanelId === gitlens.timeline",
1406114066
"group": "navigation@-99"
1406214067
},
1406314068
{
14064-
"submenu": "gitlens/graph/configuration",
14065-
"when": "activeWebviewPanelId === gitlens.graph",
14066-
"group": "navigation@-98"
14069+
"command": "gitlens.graph.split",
14070+
"when": "activeWebviewPanelId == gitlens.graph && resourceScheme == webview-panel && config.gitlens.graph.allowMultiple",
14071+
"group": "navigation@-97"
1406714072
},
1406814073
{
1406914074
"command": "gitlens.timeline.split",
@@ -14080,37 +14085,6 @@
1408014085
"when": "resource in gitlens:tabs:blameable && (gitlens:window:annotated == computing || resource in gitlens:tabs:annotated:computing) && config.gitlens.menus.editorGroup.blame",
1408114086
"group": "navigation@100"
1408214087
},
14083-
{
14084-
"command": "gitlens.diffWithPrevious",
14085-
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14086-
"group": "navigation@97",
14087-
"alt": "gitlens.diffWithRevision"
14088-
},
14089-
{
14090-
"command": "gitlens.showQuickRevisionDetails",
14091-
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14092-
"group": "navigation@98"
14093-
},
14094-
{
14095-
"command": "gitlens.diffWithNext",
14096-
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14097-
"group": "navigation@99"
14098-
},
14099-
{
14100-
"command": "gitlens.diffWithWorking",
14101-
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
14102-
"group": "navigation@-99"
14103-
},
14104-
{
14105-
"command": "gitlens.graph.refresh",
14106-
"when": "activeWebviewPanelId === gitlens.graph",
14107-
"group": "navigation@-99"
14108-
},
14109-
{
14110-
"command": "gitlens.graph.split",
14111-
"when": "activeWebviewPanelId == gitlens.graph && resourceScheme == webview-panel && config.gitlens.graph.allowMultiple",
14112-
"group": "navigation@-97"
14113-
},
1411414088
{
1411514089
"command": "gitlens.toggleFileBlame",
1411614090
"when": "resource in gitlens:tabs:blameable && resource not in gitlens:tabs:annotated && config.gitlens.menus.editorGroup.blame && config.gitlens.fileAnnotations.command == blame",
@@ -14134,16 +14108,42 @@
1413414108
"when": "resource in gitlens:tabs:blameable && resource not in gitlens:tabs:annotated && !gitlens:window:annotated && config.gitlens.menus.editorGroup.blame && !config.gitlens.fileAnnotations.command",
1413514109
"group": "navigation@100"
1413614110
},
14111+
{
14112+
"command": "gitlens.diffWithPrevious",
14113+
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14114+
"group": "navigation@97",
14115+
"alt": "gitlens.diffWithRevision"
14116+
},
14117+
{
14118+
"command": "gitlens.showQuickRevisionDetails",
14119+
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14120+
"group": "navigation@98"
14121+
},
14122+
{
14123+
"command": "gitlens.diffWithNext",
14124+
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14125+
"group": "navigation@99"
14126+
},
1413714127
{
1413814128
"command": "gitlens.openWorkingFile",
1413914129
"when": "resourceScheme == git && gitlens:enabled && !isInDiffEditor",
1414014130
"group": "navigation@-98"
1414114131
},
14132+
{
14133+
"command": "gitlens.diffWithWorking",
14134+
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
14135+
"group": "navigation@-99"
14136+
},
1414214137
{
1414314138
"command": "gitlens.openWorkingFile",
1414414139
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
1414514140
"group": "navigation@-98"
1414614141
},
14142+
{
14143+
"submenu": "gitlens/graph/configuration",
14144+
"when": "activeWebviewPanelId === gitlens.graph",
14145+
"group": "navigation@-98"
14146+
},
1414714147
{
1414814148
"command": "gitlens.openRevisionFile",
1414914149
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled && isInDiffEditor",
@@ -14174,6 +14174,26 @@
1417414174
"when": "resourceScheme == gitlens-ai-markdown && activeCustomEditorId == vscode.markdown.preview.editor && resourcePath =~ /^\\/explain\\//",
1417514175
"group": "navigation@3"
1417614176
},
14177+
{
14178+
"command": "gitlens.ai.feedback.helpful",
14179+
"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",
14180+
"group": "navigation@1"
14181+
},
14182+
{
14183+
"command": "gitlens.ai.feedback.helpful.chosen",
14184+
"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",
14185+
"group": "navigation@1"
14186+
},
14187+
{
14188+
"command": "gitlens.ai.feedback.unhelpful.chosen",
14189+
"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",
14190+
"group": "navigation@1"
14191+
},
14192+
{
14193+
"command": "gitlens.ai.feedback.unhelpful",
14194+
"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",
14195+
"group": "navigation@2"
14196+
},
1417714197
{
1417814198
"command": "gitlens.openPatch",
1417914199
"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
@@ -1036,6 +1036,7 @@ export type Sources =
10361036
| 'account'
10371037
| 'ai'
10381038
| 'ai:markdown-preview'
1039+
| 'ai:markdown-editor'
10391040
| 'ai:picker'
10401041
| 'associateIssueWithBranch'
10411042
| 'cloud-patches'

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

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Disposable, QuickInputButton } from 'vscode';
22
import { env, ThemeIcon, Uri, window } from 'vscode';
3+
import { getChangelogFeedbackContext } from '../../../../commands/generateChangelog';
34
import { Schemes } from '../../../../constants';
45
import type { AIProviders } from '../../../../constants.ai';
56
import type { Container } from '../../../../container';
@@ -282,16 +283,27 @@ export function getAIResultContext(result: AIResult): AIResultContext {
282283
}
283284

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

290-
try {
291-
const metadata = decodeGitLensRevisionUriAuthority<MarkdownContentMetadata>(authority);
292-
return metadata.context;
293-
} catch (ex) {
294-
Logger.error(ex, 'extractResultContext');
295-
return undefined;
299+
// Check for untitled documents with stored changelog feedback context
300+
if (uri?.scheme === 'untitled') {
301+
try {
302+
return getChangelogFeedbackContext(uri.toString());
303+
} catch {
304+
return undefined;
305+
}
296306
}
307+
308+
return undefined;
297309
}

0 commit comments

Comments
 (0)