Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackEditorActions.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Codicon } from '../../../../base/common/codicons.js';
7
import { localize, localize2 } from '../../../../nls.js';
8
import { Action2, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
9
import { ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
10
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
11
import { ILogService } from '../../../../platform/log/common/log.js';
12
import { URI } from '../../../../base/common/uri.js';
13
import { isEqual } from '../../../../base/common/resources.js';
14
import { EditorsOrder, IEditorIdentifier } from '../../../../workbench/common/editor.js';
15
import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js';
16
import { GroupsOrder, IEditorGroupsService } from '../../../../workbench/services/editor/common/editorGroupsService.js';
17
import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';
18
import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
19
import { CHAT_CATEGORY } from '../../../../workbench/contrib/chat/browser/actions/chatActions.js';
20
import { IAgentFeedbackService } from './agentFeedbackService.js';
21
import { getActiveResourceCandidates, getSessionForResource } from './agentFeedbackEditorUtils.js';
22
import { Menus } from '../../../browser/menus.js';
23
import { IChatEditingService } from '../../../../workbench/contrib/chat/common/editing/chatEditingService.js';
24
import { ICodeReviewService } from '../../codeReview/browser/codeReviewService.js';
25
import { getSessionEditorComments } from './sessionEditorComments.js';
26
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
27
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
28
29
export const submitFeedbackActionId = 'agentFeedbackEditor.action.submit';
30
export const navigatePreviousFeedbackActionId = 'agentFeedbackEditor.action.navigatePrevious';
31
export const navigateNextFeedbackActionId = 'agentFeedbackEditor.action.navigateNext';
32
export const clearAllFeedbackActionId = 'agentFeedbackEditor.action.clearAll';
33
export const navigationBearingFakeActionId = 'agentFeedbackEditor.navigation.bearings';
34
export const hasSessionEditorComments = new RawContextKey<boolean>('agentFeedbackEditor.hasSessionComments', false);
35
export const hasSessionAgentFeedback = new RawContextKey<boolean>('agentFeedbackEditor.hasAgentFeedback', false);
36
export const hasActiveSessionAgentFeedback = new RawContextKey<boolean>('agentFeedbackEditor.hasActiveSessionAgentFeedback', false);
37
export const submitActiveSessionFeedbackActionId = 'agentFeedbackEditor.action.submitActiveSession';
38
39
abstract class AgentFeedbackEditorAction extends Action2 {
40
41
constructor(desc: ConstructorParameters<typeof Action2>[0]) {
42
super({
43
category: CHAT_CATEGORY,
44
...desc,
45
});
46
}
47
48
override async run(accessor: ServicesAccessor): Promise<void> {
49
const editorService = accessor.get(IEditorService);
50
const agentFeedbackService = accessor.get(IAgentFeedbackService);
51
const chatEditingService = accessor.get(IChatEditingService);
52
const sessionsManagementService = accessor.get(ISessionsManagementService);
53
const codeReviewService = accessor.get(ICodeReviewService);
54
55
const editorGroupsService = accessor.get(IEditorGroupsService);
56
57
const activePane = editorService.activeEditorPane
58
?? editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(g => g.activeEditorPane)?.activeEditorPane
59
?? editorService.visibleEditorPanes[0];
60
const candidates = getActiveResourceCandidates(activePane?.input);
61
for (const candidate of candidates) {
62
const sessionResource = getSessionForResource(candidate, chatEditingService, sessionsManagementService)
63
?? agentFeedbackService.getMostRecentSessionForResource(candidate);
64
if (!sessionResource) {
65
continue;
66
}
67
68
const comments = getSessionEditorComments(
69
sessionResource,
70
agentFeedbackService.getFeedback(sessionResource),
71
codeReviewService.getReviewState(sessionResource).get(),
72
codeReviewService.getPRReviewState(sessionResource).get(),
73
);
74
if (comments.length > 0) {
75
return this.runWithSession(accessor, sessionResource);
76
}
77
}
78
}
79
80
abstract runWithSession(accessor: ServicesAccessor, sessionResource: URI): Promise<void> | void;
81
}
82
83
class SubmitFeedbackAction extends AgentFeedbackEditorAction {
84
85
constructor() {
86
super({
87
id: submitFeedbackActionId,
88
title: localize2('agentFeedback.submit', 'Submit Feedback'),
89
shortTitle: localize2('agentFeedback.submitShort', 'Submit'),
90
icon: Codicon.send,
91
precondition: ChatContextKeys.enabled,
92
menu: {
93
id: Menus.AgentFeedbackEditorContent,
94
group: 'a_submit',
95
order: 0,
96
when: ContextKeyExpr.and(ChatContextKeys.enabled, hasSessionAgentFeedback),
97
},
98
});
99
}
100
101
override async runWithSession(accessor: ServicesAccessor, sessionResource: URI): Promise<void> {
102
const chatWidgetService = accessor.get(IChatWidgetService);
103
const agentFeedbackService = accessor.get(IAgentFeedbackService);
104
const editorService = accessor.get(IEditorService);
105
const logService = accessor.get(ILogService);
106
107
const widget = chatWidgetService.getWidgetBySessionResource(sessionResource);
108
if (!widget) {
109
logService.error('[AgentFeedback] Cannot submit feedback: no chat widget found for session', sessionResource.toString());
110
return;
111
}
112
113
// Close all editors belonging to the session resource
114
const editorsToClose: IEditorIdentifier[] = [];
115
for (const { editor, groupId } of editorService.getEditors(EditorsOrder.SEQUENTIAL)) {
116
const candidates = getActiveResourceCandidates(editor);
117
const belongsToSession = candidates.some(uri =>
118
isEqual(agentFeedbackService.getMostRecentSessionForResource(uri), sessionResource)
119
);
120
if (belongsToSession) {
121
editorsToClose.push({ editor, groupId });
122
}
123
}
124
if (editorsToClose.length) {
125
await editorService.closeEditors(editorsToClose);
126
}
127
128
await widget.acceptInput('/act-on-feedback');
129
}
130
}
131
132
class NavigateFeedbackAction extends AgentFeedbackEditorAction {
133
134
constructor(private readonly _next: boolean) {
135
super({
136
id: _next ? navigateNextFeedbackActionId : navigatePreviousFeedbackActionId,
137
title: _next
138
? localize2('agentFeedback.next', 'Go to Next Feedback Comment')
139
: localize2('agentFeedback.previous', 'Go to Previous Feedback Comment'),
140
icon: _next ? Codicon.arrowDown : Codicon.arrowUp,
141
f1: true,
142
precondition: ChatContextKeys.enabled,
143
menu: {
144
id: Menus.AgentFeedbackEditorContent,
145
group: 'navigate',
146
order: _next ? 2 : 1,
147
when: ContextKeyExpr.and(ChatContextKeys.enabled, hasSessionEditorComments),
148
},
149
});
150
}
151
152
override async runWithSession(accessor: ServicesAccessor, sessionResource: URI): Promise<void> {
153
const agentFeedbackService = accessor.get(IAgentFeedbackService);
154
const codeReviewService = accessor.get(ICodeReviewService);
155
const comments = getSessionEditorComments(
156
sessionResource,
157
agentFeedbackService.getFeedback(sessionResource),
158
codeReviewService.getReviewState(sessionResource).get(),
159
codeReviewService.getPRReviewState(sessionResource).get(),
160
);
161
162
const comment = agentFeedbackService.getNextNavigableItem(sessionResource, comments, this._next);
163
if (!comment) {
164
return;
165
}
166
167
await agentFeedbackService.revealSessionComment(sessionResource, comment.id, comment.resourceUri, comment.range);
168
}
169
}
170
171
class ClearAllFeedbackAction extends AgentFeedbackEditorAction {
172
173
constructor() {
174
super({
175
id: clearAllFeedbackActionId,
176
title: localize2('agentFeedback.clear', 'Clear'),
177
tooltip: localize2('agentFeedback.clearAllTooltip', 'Clear All Feedback'),
178
icon: Codicon.clearAll,
179
f1: true,
180
precondition: ContextKeyExpr.and(ChatContextKeys.enabled),
181
menu: {
182
id: Menus.AgentFeedbackEditorContent,
183
group: 'a_submit',
184
order: 1,
185
when: ContextKeyExpr.and(ChatContextKeys.enabled, hasSessionAgentFeedback),
186
},
187
});
188
}
189
190
override runWithSession(accessor: ServicesAccessor, sessionResource: URI): void {
191
const agentFeedbackService = accessor.get(IAgentFeedbackService);
192
agentFeedbackService.clearFeedback(sessionResource);
193
}
194
}
195
196
class SubmitActiveSessionFeedbackAction extends Action2 {
197
198
static readonly ID = submitActiveSessionFeedbackActionId;
199
200
constructor() {
201
super({
202
id: SubmitActiveSessionFeedbackAction.ID,
203
title: localize2('agentFeedback.submitFeedback', 'Submit Feedback'),
204
icon: Codicon.comment,
205
category: CHAT_CATEGORY,
206
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, hasActiveSessionAgentFeedback),
207
});
208
}
209
210
override async run(accessor: ServicesAccessor): Promise<void> {
211
const sessionManagementService = accessor.get(ISessionsManagementService);
212
const configurationService = accessor.get(IConfigurationService);
213
const agentFeedbackService = accessor.get(IAgentFeedbackService);
214
const chatWidgetService = accessor.get(IChatWidgetService);
215
const editorService = accessor.get(IEditorService);
216
const logService = accessor.get(ILogService);
217
218
const activeSession = sessionManagementService.activeSession.get();
219
if (!activeSession) {
220
return;
221
}
222
223
const sessionResource = activeSession.resource;
224
const feedbackItems = agentFeedbackService.getFeedback(sessionResource);
225
if (feedbackItems.length === 0) {
226
return;
227
}
228
229
const widget = chatWidgetService.getWidgetBySessionResource(sessionResource);
230
if (!widget) {
231
logService.error('[AgentFeedback] Cannot submit feedback: no chat widget found for session', sessionResource.toString());
232
return;
233
}
234
235
// Close all editors belonging to the session resource
236
if (configurationService.getValue('workbench.editor.useModal') === 'all') {
237
const editorsToClose: IEditorIdentifier[] = [];
238
for (const { editor, groupId } of editorService.getEditors(EditorsOrder.SEQUENTIAL)) {
239
const candidates = getActiveResourceCandidates(editor);
240
const belongsToSession = candidates.some(uri =>
241
isEqual(agentFeedbackService.getMostRecentSessionForResource(uri), sessionResource)
242
);
243
if (belongsToSession) {
244
editorsToClose.push({ editor, groupId });
245
}
246
}
247
if (editorsToClose.length) {
248
await editorService.closeEditors(editorsToClose);
249
}
250
}
251
252
await widget.acceptInput('/act-on-feedback');
253
}
254
}
255
256
export function registerAgentFeedbackEditorActions(): void {
257
registerAction2(SubmitFeedbackAction);
258
registerAction2(SubmitActiveSessionFeedbackAction);
259
registerAction2(class extends NavigateFeedbackAction { constructor() { super(false); } });
260
registerAction2(class extends NavigateFeedbackAction { constructor() { super(true); } });
261
registerAction2(ClearAllFeedbackAction);
262
263
MenuRegistry.appendMenuItem(Menus.AgentFeedbackEditorContent, {
264
command: {
265
id: navigationBearingFakeActionId,
266
title: localize('label', 'Navigation Status'),
267
precondition: ContextKeyExpr.false(),
268
},
269
group: 'navigate',
270
order: -1,
271
when: ContextKeyExpr.and(ChatContextKeys.enabled, hasSessionEditorComments),
272
});
273
}
274
275