diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index c91cf01aefd..4a2455ea71f 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -8,15 +8,14 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { marked } from 'vs/base/common/marked/marked'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -import { localize, localize2 } from 'vs/nls'; -import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { localize2 } from 'vs/nls'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { ChatTreeItem, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatService, ChatAgentVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -254,89 +253,6 @@ export function registerChatTitleActions() { } } }); - - const rerunMenu = MenuId.for('ChatMessageTitle#Rerun'); - - MenuRegistry.appendMenuItem(MenuId.ChatMessageTitle, { - submenu: rerunMenu, - title: localize('reunmenu', "Rerun..."), - icon: Codicon.refresh, - group: 'navigation', - order: -10, - when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Editor)) // TODO@jrieken needs extension adoption - - }); - - registerAction2(class RerunAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chat.rerun', - title: localize2('chat.rerun.label', "Rerun Request"), - f1: false, - category: CHAT_CATEGORY, - icon: Codicon.refresh, - precondition: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Editor), // TODO@jrieken needs extension adoption - menu: { - id: rerunMenu, - group: 'navigation', - order: -1, - } - }); - } - - async run(accessor: ServicesAccessor, ...args: [ChatTreeItem | unknown]) { - const chatWidgetService = accessor.get(IChatWidgetService); - const chatService = accessor.get(IChatService); - const widget = chatWidgetService.lastFocusedWidget; - let item = args[0]; - if (!isResponseVM(item)) { - item = widget?.getFocus(); - } - if (!isResponseVM(item) || !widget) { - return; - } - const request = chatService.getSession(item.sessionId)?.getRequests().find(candidate => candidate.id === item.requestId); - if (request) { - await chatService.resendRequest(request, { noCommandDetection: false, attempt: request.attempt + 1, location: widget.location }); - } - } - }); - - registerAction2(class RerunWithoutCommandDetectionAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chat.rerunWithoutCommandDetection', - title: localize2('chat.rerunWithoutCommandDetection.label', "Rerun without Command Detection"), - f1: false, - category: CHAT_CATEGORY, - icon: Codicon.refresh, - precondition: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Editor), // TODO@jrieken needs extension adoption - menu: { - when: CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND, - id: rerunMenu, - group: 'navigation', - order: -1, - } - }); - } - - async run(accessor: ServicesAccessor, ...args: any[]) { - const chatWidgetService = accessor.get(IChatWidgetService); - const chatService = accessor.get(IChatService); - const widget = chatWidgetService.lastFocusedWidget; - let item = args[0]; - if (!isResponseVM(item)) { - item = widget?.getFocus(); - } - if (!isResponseVM(item) || !widget) { - return; - } - const request = chatService.getSession(item.sessionId)?.getRequests().find(candidate => candidate.id === item.requestId); - if (request) { - await chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt, location: widget.location }); - } - } - }); } interface MarkdownContent { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 687d7361c98..0e3c34fec24 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -78,6 +78,7 @@ import { ITrustedDomainService } from 'vs/workbench/contrib/url/browser/trustedD import { IMarkdownVulnerability, annotateSpecialMarkdownContent } from '../common/annotations'; import { CodeBlockModelCollection } from '../common/codeBlockModelCollection'; import { IChatListItemRendererOptions } from './chat'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; const $ = dom.$; @@ -124,6 +125,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer()); readonly onDidClickFollowup: Event = this._onDidClickFollowup.event; + private readonly _onDidClickRerunWithAgentOrCommandDetection = new Emitter(); + readonly onDidClickRerunWithAgentOrCommandDetection: Event = this._onDidClickRerunWithAgentOrCommandDetection.event; + protected readonly _onDidChangeItemHeight = this._register(new Emitter()); readonly onDidChangeItemHeight: Event = this._onDidChangeItemHeight.event; @@ -396,19 +400,31 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + this._onDidClickRerunWithAgentOrCommandDetection.fire(element); + }, + } + })); - templateData.detail.textContent = progressMsg; + } else if (!element.isComplete) { + templateData.detail.textContent = GeneratingPhrase; + } } private renderAvatar(element: ChatTreeItem, templateData: IChatListItemTemplate): void { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 30432395fe3..7144f1b7948 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -436,6 +436,12 @@ export class ChatWidget extends Disposable implements IChatWidget { // is this used anymore? this.acceptInput(item.message); })); + this._register(this.renderer.onDidClickRerunWithAgentOrCommandDetection(item => { + const request = this.chatService.getSession(item.sessionId)?.getRequests().find(candidate => candidate.id === item.requestId); + if (request) { + this.chatService.resendRequest(request, { noCommandDetection: true, attempt: request.attempt, location: this.location }).catch(e => this.logService.error('FAILED to rerun request', e)); + } + })); this.tree = >scopedInstantiationService.createInstance( WorkbenchObjectTree, diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 391a2bcf4b9..8b09ad3c402 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -53,6 +53,11 @@ color: var(--vscode-descriptionForeground); } +.interactive-item-container .header .detail-container .detail .agentOrSlashCommandDetected A { + cursor: pointer; + color: var(--vscode-textLink-foreground); +} + .interactive-item-container .chat-animated-ellipsis { display: inline-block; width: 11px; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index 5effa9d61b6..2044d45b2e3 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -40,6 +40,7 @@ registerAction2(InlineChatActions.DiscardHunkAction); registerAction2(InlineChatActions.DiscardAction); registerAction2(InlineChatActions.DiscardToClipboardAction); registerAction2(InlineChatActions.DiscardUndoToNewFileAction); +registerAction2(InlineChatActions.RerunAction); registerAction2(InlineChatActions.CancelSessionAction); registerAction2(InlineChatActions.MoveToNextHunk); registerAction2(InlineChatActions.MoveToPreviousHunk); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 7ff7d0ab205..fe495060020 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -11,7 +11,7 @@ import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/em import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET, ACTION_TOGGLE_DIFF } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET, ACTION_TOGGLE_DIFF, ACTION_REGENERATE_RESPONSE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -29,6 +29,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ILogService } from 'vs/platform/log/common/log'; +import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -356,7 +357,8 @@ export class ToggleDiffForChange extends AbstractInlineChatAction { { id: MENU_INLINE_CHAT_WIDGET_STATUS, group: '1_main', - when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF) + when: ContextKeyExpr.and(CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live), CTX_INLINE_CHAT_CHANGE_HAS_DIFF), + order: 10, } ] }); @@ -564,3 +566,28 @@ export class ViewInChatAction extends AbstractInlineChatAction { } } +export class RerunAction extends AbstractInlineChatAction { + constructor() { + super({ + id: ACTION_REGENERATE_RESPONSE, + title: localize2('chat.rerun.label', "Rerun Request"), + f1: false, + icon: Codicon.refresh, + menu: { + id: MENU_INLINE_CHAT_WIDGET_STATUS, + group: '0_main', + order: 5, + } + }); + } + + override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { + const chatService = accessor.get(IChatService); + const model = ctrl.chatWidget.viewModel?.model; + + const lastRequest = model?.getRequests().at(-1); + if (lastRequest) { + await chatService.resendRequest(lastRequest, { noCommandDetection: false, attempt: lastRequest.attempt + 1, location: ctrl.chatWidget.location }); + } + } +}