Path: blob/main/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackEditorActions.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Codicon } from '../../../../base/common/codicons.js';6import { localize, localize2 } from '../../../../nls.js';7import { Action2, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';8import { ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';9import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';10import { ILogService } from '../../../../platform/log/common/log.js';11import { URI } from '../../../../base/common/uri.js';12import { isEqual } from '../../../../base/common/resources.js';13import { EditorsOrder, IEditorIdentifier } from '../../../../workbench/common/editor.js';14import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js';15import { GroupsOrder, IEditorGroupsService } from '../../../../workbench/services/editor/common/editorGroupsService.js';16import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';17import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';18import { CHAT_CATEGORY } from '../../../../workbench/contrib/chat/browser/actions/chatActions.js';19import { IAgentFeedbackService } from './agentFeedbackService.js';20import { getActiveResourceCandidates, getSessionForResource } from './agentFeedbackEditorUtils.js';21import { Menus } from '../../../browser/menus.js';22import { IChatEditingService } from '../../../../workbench/contrib/chat/common/editing/chatEditingService.js';23import { ICodeReviewService } from '../../codeReview/browser/codeReviewService.js';24import { getSessionEditorComments } from './sessionEditorComments.js';25import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';26import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';2728export const submitFeedbackActionId = 'agentFeedbackEditor.action.submit';29export const navigatePreviousFeedbackActionId = 'agentFeedbackEditor.action.navigatePrevious';30export const navigateNextFeedbackActionId = 'agentFeedbackEditor.action.navigateNext';31export const clearAllFeedbackActionId = 'agentFeedbackEditor.action.clearAll';32export const navigationBearingFakeActionId = 'agentFeedbackEditor.navigation.bearings';33export const hasSessionEditorComments = new RawContextKey<boolean>('agentFeedbackEditor.hasSessionComments', false);34export const hasSessionAgentFeedback = new RawContextKey<boolean>('agentFeedbackEditor.hasAgentFeedback', false);35export const hasActiveSessionAgentFeedback = new RawContextKey<boolean>('agentFeedbackEditor.hasActiveSessionAgentFeedback', false);36export const submitActiveSessionFeedbackActionId = 'agentFeedbackEditor.action.submitActiveSession';3738abstract class AgentFeedbackEditorAction extends Action2 {3940constructor(desc: ConstructorParameters<typeof Action2>[0]) {41super({42category: CHAT_CATEGORY,43...desc,44});45}4647override async run(accessor: ServicesAccessor): Promise<void> {48const editorService = accessor.get(IEditorService);49const agentFeedbackService = accessor.get(IAgentFeedbackService);50const chatEditingService = accessor.get(IChatEditingService);51const sessionsManagementService = accessor.get(ISessionsManagementService);52const codeReviewService = accessor.get(ICodeReviewService);5354const editorGroupsService = accessor.get(IEditorGroupsService);5556const activePane = editorService.activeEditorPane57?? editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).find(g => g.activeEditorPane)?.activeEditorPane58?? editorService.visibleEditorPanes[0];59const candidates = getActiveResourceCandidates(activePane?.input);60for (const candidate of candidates) {61const sessionResource = getSessionForResource(candidate, chatEditingService, sessionsManagementService)62?? agentFeedbackService.getMostRecentSessionForResource(candidate);63if (!sessionResource) {64continue;65}6667const comments = getSessionEditorComments(68sessionResource,69agentFeedbackService.getFeedback(sessionResource),70codeReviewService.getReviewState(sessionResource).get(),71codeReviewService.getPRReviewState(sessionResource).get(),72);73if (comments.length > 0) {74return this.runWithSession(accessor, sessionResource);75}76}77}7879abstract runWithSession(accessor: ServicesAccessor, sessionResource: URI): Promise<void> | void;80}8182class SubmitFeedbackAction extends AgentFeedbackEditorAction {8384constructor() {85super({86id: submitFeedbackActionId,87title: localize2('agentFeedback.submit', 'Submit Feedback'),88shortTitle: localize2('agentFeedback.submitShort', 'Submit'),89icon: Codicon.send,90precondition: ChatContextKeys.enabled,91menu: {92id: Menus.AgentFeedbackEditorContent,93group: 'a_submit',94order: 0,95when: ContextKeyExpr.and(ChatContextKeys.enabled, hasSessionAgentFeedback),96},97});98}99100override async runWithSession(accessor: ServicesAccessor, sessionResource: URI): Promise<void> {101const chatWidgetService = accessor.get(IChatWidgetService);102const agentFeedbackService = accessor.get(IAgentFeedbackService);103const editorService = accessor.get(IEditorService);104const logService = accessor.get(ILogService);105106const widget = chatWidgetService.getWidgetBySessionResource(sessionResource);107if (!widget) {108logService.error('[AgentFeedback] Cannot submit feedback: no chat widget found for session', sessionResource.toString());109return;110}111112// Close all editors belonging to the session resource113const editorsToClose: IEditorIdentifier[] = [];114for (const { editor, groupId } of editorService.getEditors(EditorsOrder.SEQUENTIAL)) {115const candidates = getActiveResourceCandidates(editor);116const belongsToSession = candidates.some(uri =>117isEqual(agentFeedbackService.getMostRecentSessionForResource(uri), sessionResource)118);119if (belongsToSession) {120editorsToClose.push({ editor, groupId });121}122}123if (editorsToClose.length) {124await editorService.closeEditors(editorsToClose);125}126127await widget.acceptInput('/act-on-feedback');128}129}130131class NavigateFeedbackAction extends AgentFeedbackEditorAction {132133constructor(private readonly _next: boolean) {134super({135id: _next ? navigateNextFeedbackActionId : navigatePreviousFeedbackActionId,136title: _next137? localize2('agentFeedback.next', 'Go to Next Feedback Comment')138: localize2('agentFeedback.previous', 'Go to Previous Feedback Comment'),139icon: _next ? Codicon.arrowDown : Codicon.arrowUp,140f1: true,141precondition: ChatContextKeys.enabled,142menu: {143id: Menus.AgentFeedbackEditorContent,144group: 'navigate',145order: _next ? 2 : 1,146when: ContextKeyExpr.and(ChatContextKeys.enabled, hasSessionEditorComments),147},148});149}150151override async runWithSession(accessor: ServicesAccessor, sessionResource: URI): Promise<void> {152const agentFeedbackService = accessor.get(IAgentFeedbackService);153const codeReviewService = accessor.get(ICodeReviewService);154const comments = getSessionEditorComments(155sessionResource,156agentFeedbackService.getFeedback(sessionResource),157codeReviewService.getReviewState(sessionResource).get(),158codeReviewService.getPRReviewState(sessionResource).get(),159);160161const comment = agentFeedbackService.getNextNavigableItem(sessionResource, comments, this._next);162if (!comment) {163return;164}165166await agentFeedbackService.revealSessionComment(sessionResource, comment.id, comment.resourceUri, comment.range);167}168}169170class ClearAllFeedbackAction extends AgentFeedbackEditorAction {171172constructor() {173super({174id: clearAllFeedbackActionId,175title: localize2('agentFeedback.clear', 'Clear'),176tooltip: localize2('agentFeedback.clearAllTooltip', 'Clear All Feedback'),177icon: Codicon.clearAll,178f1: true,179precondition: ContextKeyExpr.and(ChatContextKeys.enabled),180menu: {181id: Menus.AgentFeedbackEditorContent,182group: 'a_submit',183order: 1,184when: ContextKeyExpr.and(ChatContextKeys.enabled, hasSessionAgentFeedback),185},186});187}188189override runWithSession(accessor: ServicesAccessor, sessionResource: URI): void {190const agentFeedbackService = accessor.get(IAgentFeedbackService);191agentFeedbackService.clearFeedback(sessionResource);192}193}194195class SubmitActiveSessionFeedbackAction extends Action2 {196197static readonly ID = submitActiveSessionFeedbackActionId;198199constructor() {200super({201id: SubmitActiveSessionFeedbackAction.ID,202title: localize2('agentFeedback.submitFeedback', 'Submit Feedback'),203icon: Codicon.comment,204category: CHAT_CATEGORY,205precondition: ContextKeyExpr.and(ChatContextKeys.enabled, hasActiveSessionAgentFeedback),206});207}208209override async run(accessor: ServicesAccessor): Promise<void> {210const sessionManagementService = accessor.get(ISessionsManagementService);211const configurationService = accessor.get(IConfigurationService);212const agentFeedbackService = accessor.get(IAgentFeedbackService);213const chatWidgetService = accessor.get(IChatWidgetService);214const editorService = accessor.get(IEditorService);215const logService = accessor.get(ILogService);216217const activeSession = sessionManagementService.activeSession.get();218if (!activeSession) {219return;220}221222const sessionResource = activeSession.resource;223const feedbackItems = agentFeedbackService.getFeedback(sessionResource);224if (feedbackItems.length === 0) {225return;226}227228const widget = chatWidgetService.getWidgetBySessionResource(sessionResource);229if (!widget) {230logService.error('[AgentFeedback] Cannot submit feedback: no chat widget found for session', sessionResource.toString());231return;232}233234// Close all editors belonging to the session resource235if (configurationService.getValue('workbench.editor.useModal') === 'all') {236const editorsToClose: IEditorIdentifier[] = [];237for (const { editor, groupId } of editorService.getEditors(EditorsOrder.SEQUENTIAL)) {238const candidates = getActiveResourceCandidates(editor);239const belongsToSession = candidates.some(uri =>240isEqual(agentFeedbackService.getMostRecentSessionForResource(uri), sessionResource)241);242if (belongsToSession) {243editorsToClose.push({ editor, groupId });244}245}246if (editorsToClose.length) {247await editorService.closeEditors(editorsToClose);248}249}250251await widget.acceptInput('/act-on-feedback');252}253}254255export function registerAgentFeedbackEditorActions(): void {256registerAction2(SubmitFeedbackAction);257registerAction2(SubmitActiveSessionFeedbackAction);258registerAction2(class extends NavigateFeedbackAction { constructor() { super(false); } });259registerAction2(class extends NavigateFeedbackAction { constructor() { super(true); } });260registerAction2(ClearAllFeedbackAction);261262MenuRegistry.appendMenuItem(Menus.AgentFeedbackEditorContent, {263command: {264id: navigationBearingFakeActionId,265title: localize('label', 'Navigation Status'),266precondition: ContextKeyExpr.false(),267},268group: 'navigate',269order: -1,270when: ContextKeyExpr.and(ChatContextKeys.enabled, hasSessionEditorComments),271});272}273274275