From bd888a0842128eb80d8a44b0ae7439cf042ce334 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 24 May 2023 16:33:46 -0700 Subject: [PATCH] Implement "remove request/response" for Chat --- src/vs/platform/actions/common/actions.ts | 2 +- .../workbench/api/browser/mainThreadChat.ts | 7 ++- .../workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostChat.ts | 23 +++++++- .../chat/browser/actions/chatTitleActions.ts | 53 ++++++++++++++++--- .../contrib/chat/browser/chatListRenderer.ts | 8 +-- .../contrib/chat/browser/media/chat.css | 4 +- .../contrib/chat/common/chatContextKeys.ts | 3 ++ .../contrib/chat/common/chatModel.ts | 45 +++++++++++++--- .../contrib/chat/common/chatService.ts | 4 +- .../contrib/chat/common/chatServiceImpl.ts | 18 ++++++- .../contrib/chat/common/chatViewModel.ts | 33 +++++++++++- .../vscode.proposed.interactive.d.ts | 3 ++ 13 files changed, 175 insertions(+), 29 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 18e0c99f060ca..df156d0bbc73d 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -183,7 +183,7 @@ export class MenuId { static readonly InlineSuggestionToolbar = new MenuId('InlineSuggestionToolbar'); static readonly ChatContext = new MenuId('ChatContext'); static readonly ChatCodeBlock = new MenuId('ChatCodeblock'); - static readonly ChatTitle = new MenuId('ChatTitle'); + static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); static readonly ChatExecute = new MenuId('ChatExecute'); /** diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index fc6e18b077de0..807882e08674c 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -8,10 +8,10 @@ import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; -import { ExtHostContext, ExtHostChatShape, IChatRequestDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatShape, ExtHostContext, IChatRequestDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { IChatProgress, IChatRequest, IChatResponse, IChat, IChatDynamicRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatDynamicRequest, IChatProgress, IChatRequest, IChatResponse, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChat) @@ -134,6 +134,9 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { }, provideFollowups: (session, token) => { return this._proxy.$provideFollowups(handle, session.id, token); + }, + removeRequest: (session, requestId) => { + return this._proxy.$removeRequest(handle, session.id, requestId); } }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a689fa3397670..b46afc957e642 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1174,6 +1174,7 @@ export interface ExtHostChatShape { $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IChatReplyFollowup[])[] | undefined>; $provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise; $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise; + $removeRequest(handle: number, sessionId: number, requestId: string): void; $provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise; $releaseSession(sessionId: number): void; $onDidPerformUserAction(event: IChatUserActionEvent): Promise; diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index b4aada3a1a0ae..2a623150731a6 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -13,7 +13,7 @@ import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/exte import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatShape, IChatRequestDto, IChatResponseDto, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; import type * as vscode from 'vscode'; class ChatProviderWrapper { @@ -158,6 +158,24 @@ export class ExtHostChat implements ExtHostChatShape { return rawFollowups?.map(f => typeConvert.ChatFollowup.from(f)); } + $removeRequest(handle: number, sessionId: number, requestId: string): void { + const entry = this._chatProvider.get(handle); + if (!entry) { + return; + } + + const realSession = this._chatSessions.get(sessionId); + if (!realSession) { + return; + } + + if (!entry.provider.removeRequest) { + return; + } + + entry.provider.removeRequest(realSession, requestId); + } + async $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise { const entry = this._chatProvider.get(handle); if (!entry) { @@ -186,7 +204,8 @@ export class ExtHostChat implements ExtHostChatShape { firstProgress = stopWatch.elapsed(); } - this._proxy.$acceptResponseProgress(handle, sessionId, progress); + const vscodeProgress: IChatProgress = 'responseId' in progress ? { requestId: progress.responseId } : progress; + this._proxy.$acceptResponseProgress(handle, sessionId, vscodeProgress); } }; let result: vscode.InteractiveResponseForProgress | undefined | null; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 40b6a5dc088eb..222d89dc6821f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -9,11 +9,12 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatService, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; -import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellEditType, CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; @@ -33,9 +34,10 @@ export function registerChatTitleActions() { icon: Codicon.thumbsup, toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('up'), menu: { - id: MenuId.ChatTitle, + id: MenuId.ChatMessageTitle, group: 'navigation', - order: 1 + order: 1, + when: CONTEXT_RESPONSE } }); } @@ -72,9 +74,10 @@ export function registerChatTitleActions() { icon: Codicon.thumbsdown, toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('down'), menu: { - id: MenuId.ChatTitle, + id: MenuId.ChatMessageTitle, group: 'navigation', - order: 2 + order: 2, + when: CONTEXT_RESPONSE } }); } @@ -110,10 +113,10 @@ export function registerChatTitleActions() { category: CHAT_CATEGORY, icon: Codicon.insert, menu: { - id: MenuId.ChatTitle, + id: MenuId.ChatMessageTitle, group: 'navigation', isHiddenByDefault: true, - when: NOTEBOOK_IS_ACTIVE_EDITOR + when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, CONTEXT_RESPONSE) } }); } @@ -172,6 +175,40 @@ export function registerChatTitleActions() { } } }); + + + registerAction2(class RemoveAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.remove', + title: { + value: localize('chat.remove.label', "Remove Request and Response"), + original: 'Remove Request and Response' + }, + f1: false, + category: CHAT_CATEGORY, + icon: Codicon.x, + menu: { + id: MenuId.ChatMessageTitle, + group: 'navigation', + order: 2, + when: CONTEXT_REQUEST + } + }); + } + + run(accessor: ServicesAccessor, ...args: any[]) { + const item = args[0]; + if (!isRequestVM(item)) { + return; + } + + const chatService = accessor.get(IChatService); + if (item.providerRequestId) { + chatService.removeRequest(item.sessionId, item.providerRequestId); + } + } + }); } interface MarkdownContent { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index f8decdfdd46a6..312f404312451 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -54,7 +54,7 @@ import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/a import { IChatCodeBlockInfo } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatReplyFollowup, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestViewModel, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; @@ -192,12 +192,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - if (action instanceof MenuItemAction) { + if (action instanceof MenuItemAction && (action.item.id === 'workbench.action.chat.voteDown' || action.item.id === 'workbench.action.chat.voteUp')) { return scopedInstantiationService.createInstance(ChatVoteButton, action, options as IMenuEntryActionViewItemOptions); } @@ -217,6 +217,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer