Skip to content

Commit c58ec7b

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 08b26bc commit c58ec7b

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
@@ -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
@@ -14124,15 +14124,20 @@
1412414124
}
1412514125
],
1412614126
"editor/title": [
14127+
{
14128+
"command": "gitlens.graph.refresh",
14129+
"when": "activeWebviewPanelId === gitlens.graph",
14130+
"group": "navigation@-99"
14131+
},
1412714132
{
1412814133
"command": "gitlens.timeline.refresh",
1412914134
"when": "activeWebviewPanelId === gitlens.timeline",
1413014135
"group": "navigation@-99"
1413114136
},
1413214137
{
14133-
"submenu": "gitlens/graph/configuration",
14134-
"when": "activeWebviewPanelId === gitlens.graph",
14135-
"group": "navigation@-98"
14138+
"command": "gitlens.graph.split",
14139+
"when": "activeWebviewPanelId == gitlens.graph && resourceScheme == webview-panel && config.gitlens.graph.allowMultiple",
14140+
"group": "navigation@-97"
1413614141
},
1413714142
{
1413814143
"command": "gitlens.timeline.split",
@@ -14149,37 +14154,6 @@
1414914154
"when": "resource in gitlens:tabs:blameable && (gitlens:window:annotated == computing || resource in gitlens:tabs:annotated:computing) && config.gitlens.menus.editorGroup.blame",
1415014155
"group": "navigation@100"
1415114156
},
14152-
{
14153-
"command": "gitlens.diffWithPrevious",
14154-
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14155-
"group": "navigation@97",
14156-
"alt": "gitlens.diffWithRevision"
14157-
},
14158-
{
14159-
"command": "gitlens.showQuickRevisionDetails",
14160-
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14161-
"group": "navigation@98"
14162-
},
14163-
{
14164-
"command": "gitlens.diffWithNext",
14165-
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14166-
"group": "navigation@99"
14167-
},
14168-
{
14169-
"command": "gitlens.diffWithWorking",
14170-
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
14171-
"group": "navigation@-99"
14172-
},
14173-
{
14174-
"command": "gitlens.graph.refresh",
14175-
"when": "activeWebviewPanelId === gitlens.graph",
14176-
"group": "navigation@-99"
14177-
},
14178-
{
14179-
"command": "gitlens.graph.split",
14180-
"when": "activeWebviewPanelId == gitlens.graph && resourceScheme == webview-panel && config.gitlens.graph.allowMultiple",
14181-
"group": "navigation@-97"
14182-
},
1418314157
{
1418414158
"command": "gitlens.toggleFileBlame",
1418514159
"when": "resource in gitlens:tabs:blameable && resource not in gitlens:tabs:annotated && config.gitlens.menus.editorGroup.blame && config.gitlens.fileAnnotations.command == blame",
@@ -14203,16 +14177,42 @@
1420314177
"when": "resource in gitlens:tabs:blameable && resource not in gitlens:tabs:annotated && !gitlens:window:annotated && config.gitlens.menus.editorGroup.blame && !config.gitlens.fileAnnotations.command",
1420414178
"group": "navigation@100"
1420514179
},
14180+
{
14181+
"command": "gitlens.diffWithPrevious",
14182+
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14183+
"group": "navigation@97",
14184+
"alt": "gitlens.diffWithRevision"
14185+
},
14186+
{
14187+
"command": "gitlens.showQuickRevisionDetails",
14188+
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14189+
"group": "navigation@98"
14190+
},
14191+
{
14192+
"command": "gitlens.diffWithNext",
14193+
"when": "resource in gitlens:tabs:tracked && config.gitlens.menus.editorGroup.compare",
14194+
"group": "navigation@99"
14195+
},
1420614196
{
1420714197
"command": "gitlens.openWorkingFile",
1420814198
"when": "resourceScheme == git && gitlens:enabled && !isInDiffEditor",
1420914199
"group": "navigation@-98"
1421014200
},
14201+
{
14202+
"command": "gitlens.diffWithWorking",
14203+
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
14204+
"group": "navigation@-99"
14205+
},
1421114206
{
1421214207
"command": "gitlens.openWorkingFile",
1421314208
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled",
1421414209
"group": "navigation@-98"
1421514210
},
14211+
{
14212+
"submenu": "gitlens/graph/configuration",
14213+
"when": "activeWebviewPanelId === gitlens.graph",
14214+
"group": "navigation@-98"
14215+
},
1421614216
{
1421714217
"command": "gitlens.openRevisionFile",
1421814218
"when": "resourceScheme =~ /^(gitlens|pr)$/ && gitlens:enabled && isInDiffEditor",
@@ -14243,6 +14243,26 @@
1424314243
"when": "resourceScheme == gitlens-ai-markdown && activeCustomEditorId == vscode.markdown.preview.editor && resourcePath =~ /^\\/explain\\//",
1424414244
"group": "navigation@3"
1424514245
},
14246+
{
14247+
"command": "gitlens.ai.feedback.helpful",
14248+
"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",
14249+
"group": "navigation@1"
14250+
},
14251+
{
14252+
"command": "gitlens.ai.feedback.helpful.chosen",
14253+
"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",
14254+
"group": "navigation@1"
14255+
},
14256+
{
14257+
"command": "gitlens.ai.feedback.unhelpful.chosen",
14258+
"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",
14259+
"group": "navigation@1"
14260+
},
14261+
{
14262+
"command": "gitlens.ai.feedback.unhelpful",
14263+
"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",
14264+
"group": "navigation@2"
14265+
},
1424614266
{
1424714267
"command": "gitlens.openPatch",
1424814268
"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: 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';
@@ -283,16 +284,27 @@ export function getAIResultContext(result: AIResult): AIResultContext {
283284
}
284285

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

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

0 commit comments

Comments
 (0)