Skip to content

Commit 68c5646

Browse files
authored
tools: move MCP link to footing menu (microsoft#259913)
Closes microsoft/vscode-copilot#16836
1 parent 32c3172 commit 68c5646

15 files changed

+139
-63
lines changed

src/vs/platform/actions/common/actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ export class MenuId {
256256
static readonly ChatTerminalMenu = new MenuId('ChatTerminalMenu');
257257
static readonly ChatToolOutputResourceContext = new MenuId('ChatToolOutputResourceContext');
258258
static readonly ChatSessionsMenu = new MenuId('ChatSessionsMenu');
259+
static readonly ChatConfirmationMenu = new MenuId('ChatConfirmationMenu');
259260
static readonly AccessibleView = new MenuId('AccessibleView');
260261
static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar');
261262
static readonly DiffEditorHunkToolbar = new MenuId('DiffEditorHunkToolbar');

src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationContentPart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class ChatConfirmationContentPart extends Disposable implements IChatCont
3939
{ label: localize('accept', "Accept"), data: confirmation.data },
4040
{ label: localize('dismiss', "Dismiss"), data: confirmation.data, isSecondary: true },
4141
];
42-
const confirmationWidget = this._register(this.instantiationService.createInstance(ChatConfirmationWidget, confirmation.title, undefined, confirmation.message, buttons, context.container));
42+
const confirmationWidget = this._register(this.instantiationService.createInstance(ChatConfirmationWidget, context.container, { title: confirmation.title, buttons, message: confirmation.message }));
4343
confirmationWidget.setShowButtons(!confirmation.isUsed);
4444

4545
this._register(confirmationWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire()));

src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ import { IMarkdownString, MarkdownString } from '../../../../../base/common/html
1212
import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js';
1313
import { IMarkdownRenderResult, MarkdownRenderer, openLinkFromMarkdown } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js';
1414
import { localize } from '../../../../../nls.js';
15+
import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js';
16+
import { MenuId } from '../../../../../platform/actions/common/actions.js';
1517
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
18+
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
1619
import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js';
1720
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
21+
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
1822
import { FocusMode } from '../../../../../platform/native/common/native.js';
1923
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
2024
import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js';
@@ -33,6 +37,13 @@ export interface IChatConfirmationButton {
3337
moreActions?: IChatConfirmationButton[];
3438
}
3539

40+
export interface IChatConfirmationWidgetOptions {
41+
title: string | IMarkdownString;
42+
subtitle?: string | IMarkdownString;
43+
buttons: IChatConfirmationButton[];
44+
toolbarData?: { arg: any; partType: string };
45+
}
46+
3647
export class ChatQueryTitlePart extends Disposable {
3748
private readonly _onDidChangeHeight = this._register(new Emitter<void>());
3849
public readonly onDidChangeHeight = this._onDidChangeHeight.event;
@@ -117,25 +128,31 @@ abstract class BaseChatConfirmationWidget extends Disposable {
117128

118129
private readonly messageElement: HTMLElement;
119130
protected readonly markdownRenderer: MarkdownRenderer;
131+
private readonly title: string | IMarkdownString;
120132

121133
private readonly notification = this._register(new MutableDisposable<DisposableStore>());
122134

123135
constructor(
124-
private title: string | IMarkdownString,
125-
subtitle: string | IMarkdownString | undefined,
126-
buttons: IChatConfirmationButton[],
136+
options: IChatConfirmationWidgetOptions,
127137
@IInstantiationService protected readonly instantiationService: IInstantiationService,
128138
@IContextMenuService contextMenuService: IContextMenuService,
129139
@IConfigurationService private readonly _configurationService: IConfigurationService,
130140
@IHostService private readonly _hostService: IHostService,
131-
@IViewsService private readonly _viewsService: IViewsService
141+
@IViewsService private readonly _viewsService: IViewsService,
142+
@IContextKeyService contextKeyService: IContextKeyService,
132143
) {
133144
super();
134145

146+
const { title, subtitle, buttons } = options;
147+
this.title = title;
148+
135149
const elements = dom.h('.chat-confirmation-widget@root', [
136150
dom.h('.chat-confirmation-widget-title@title'),
137151
dom.h('.chat-confirmation-widget-message@message'),
138-
dom.h('.chat-buttons-container@buttonsContainer'),
152+
dom.h('.chat-buttons-container@buttonsContainer', [
153+
dom.h('.chat-buttons@buttons'),
154+
dom.h('.chat-toolbar@toolbar'),
155+
]),
139156
]);
140157
this._domNode = elements.root;
141158
this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {});
@@ -151,12 +168,14 @@ abstract class BaseChatConfirmationWidget extends Disposable {
151168
this._register(titlePart.onDidChangeHeight(() => this._onDidChangeHeight.fire()));
152169

153170
this.messageElement = elements.message;
171+
172+
// Create buttons
154173
buttons.forEach(buttonData => {
155174
const buttonOptions: IButtonOptions = { ...defaultButtonStyles, secondary: buttonData.isSecondary, title: buttonData.tooltip, disabled: buttonData.disabled };
156175

157176
let button: IButton;
158177
if (buttonData.moreActions) {
159-
button = new ButtonWithDropdown(elements.buttonsContainer, {
178+
button = new ButtonWithDropdown(elements.buttons, {
160179
...buttonOptions,
161180
contextMenuProvider: contextMenuService,
162181
addPrimaryActionToDropdown: false,
@@ -172,7 +191,7 @@ abstract class BaseChatConfirmationWidget extends Disposable {
172191
))),
173192
});
174193
} else {
175-
button = new Button(elements.buttonsContainer, buttonOptions);
194+
button = new Button(elements.buttons, buttonOptions);
176195
}
177196

178197
this._register(button);
@@ -182,6 +201,24 @@ abstract class BaseChatConfirmationWidget extends Disposable {
182201
this._register(buttonData.onDidChangeDisablement(disabled => button.enabled = !disabled));
183202
}
184203
});
204+
205+
// Create toolbar if actions are provided
206+
if (options?.toolbarData) {
207+
const overlay = contextKeyService.createOverlay([['chatConfirmationPartType', options.toolbarData.partType]]);
208+
const nestedInsta = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, overlay])));
209+
this._register(nestedInsta.createInstance(
210+
MenuWorkbenchToolBar,
211+
elements.toolbar,
212+
MenuId.ChatConfirmationMenu,
213+
{
214+
// buttonConfigProvider: () => ({ showLabel: false, showIcon: true }),
215+
menuOptions: {
216+
arg: options.toolbarData.arg,
217+
shouldForwardArgs: true,
218+
}
219+
}
220+
));
221+
}
185222
}
186223

187224
protected renderMessage(element: HTMLElement, listContainer: HTMLElement): void {
@@ -224,24 +261,21 @@ abstract class BaseChatConfirmationWidget extends Disposable {
224261
}
225262
}
226263
}
227-
228264
export class ChatConfirmationWidget extends BaseChatConfirmationWidget {
229265
private _renderedMessage: HTMLElement | undefined;
230266

231267
constructor(
232-
title: string | IMarkdownString,
233-
subtitle: string | IMarkdownString | undefined,
234-
message: string | IMarkdownString,
235-
buttons: IChatConfirmationButton[],
236268
private readonly _container: HTMLElement,
269+
options: IChatConfirmationWidgetOptions & { message: string | IMarkdownString },
237270
@IInstantiationService instantiationService: IInstantiationService,
238271
@IContextMenuService contextMenuService: IContextMenuService,
239272
@IConfigurationService configurationService: IConfigurationService,
240273
@IHostService hostService: IHostService,
241-
@IViewsService viewsService: IViewsService
274+
@IViewsService viewsService: IViewsService,
275+
@IContextKeyService contextKeyService: IContextKeyService,
242276
) {
243-
super(title, subtitle, buttons, instantiationService, contextMenuService, configurationService, hostService, viewsService);
244-
this.updateMessage(message);
277+
super(options, instantiationService, contextMenuService, configurationService, hostService, viewsService, contextKeyService);
278+
this.updateMessage(options.message);
245279
}
246280

247281
public updateMessage(message: string | IMarkdownString): void {
@@ -257,18 +291,16 @@ export class ChatConfirmationWidget extends BaseChatConfirmationWidget {
257291

258292
export class ChatCustomConfirmationWidget extends BaseChatConfirmationWidget {
259293
constructor(
260-
title: string | IMarkdownString,
261-
subtitle: string | IMarkdownString | undefined,
262-
messageElement: HTMLElement,
263-
buttons: IChatConfirmationButton[],
264294
container: HTMLElement,
295+
options: IChatConfirmationWidgetOptions & { message: HTMLElement },
265296
@IInstantiationService instantiationService: IInstantiationService,
266297
@IContextMenuService contextMenuService: IContextMenuService,
267298
@IConfigurationService configurationService: IConfigurationService,
268299
@IHostService hostService: IHostService,
269-
@IViewsService viewsService: IViewsService
300+
@IViewsService viewsService: IViewsService,
301+
@IContextKeyService contextKeyService: IContextKeyService,
270302
) {
271-
super(title, subtitle, buttons, instantiationService, contextMenuService, configurationService, hostService, viewsService);
272-
this.renderMessage(messageElement, container);
303+
super(options, instantiationService, contextMenuService, configurationService, hostService, viewsService, contextKeyService);
304+
this.renderMessage(options.message, container);
273305
}
274306
}

src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class ChatElicitationContentPart extends Disposable implements IChatConte
3131
{ label: elicitation.acceptButtonLabel, data: true },
3232
{ label: elicitation.rejectButtonLabel, data: false, isSecondary: true },
3333
];
34-
const confirmationWidget = this._register(this.instantiationService.createInstance(ChatConfirmationWidget, elicitation.title, elicitation.originMessage, this.getMessageToRender(elicitation), buttons, context.container));
34+
const confirmationWidget = this._register(this.instantiationService.createInstance(ChatConfirmationWidget, context.container, { title: elicitation.title, subtitle: elicitation.subtitle, buttons, message: this.getMessageToRender(elicitation), toolbarData: { partType: elicitation.source ? `${elicitation.source.type}Elicitation` : 'elicitation', arg: elicitation } }));
3535
confirmationWidget.setShowButtons(elicitation.state === 'pending');
3636

3737
this._register(elicitation.onDidRequestHide(() => this.domNode.remove()));

src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@
1717
}
1818

1919
.chat-confirmation-widget .chat-confirmation-widget-title {
20-
display: flex;
21-
align-items: center;
2220
width: 100%;
2321
border-radius: 3px;
2422
padding: 3px 8px;
2523
user-select: none;
26-
gap: 4px;
27-
border: none;
24+
25+
&.monaco-button {
26+
display: flex;
27+
align-items: center;
28+
border: 0;
29+
}
2830
}
2931

3032
.chat-confirmation-widget .chat-confirmation-widget-title.expandable {
@@ -108,6 +110,23 @@
108110
}
109111
}
110112

113+
.chat-confirmation-widget .chat-buttons-container {
114+
display: flex;
115+
justify-content: space-between;
116+
align-items: center;
117+
gap: 8px;
118+
}
119+
120+
.chat-confirmation-widget .chat-buttons {
121+
display: flex;
122+
gap: 8px;
123+
}
124+
125+
.chat-confirmation-widget .chat-toolbar {
126+
display: flex;
127+
align-items: center;
128+
}
129+
111130
.chat-confirmation-widget.collapsed .chat-confirmation-widget-message {
112131
display: none;
113132
}

src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatExtensionsInstallToolSubPart.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,12 @@ export class ExtensionsInstallConfirmationWidgetSubPart extends BaseChatToolInvo
7474

7575
const confirmWidget = this._register(instantiationService.createInstance(
7676
ChatConfirmationWidget,
77-
toolInvocation.confirmationMessages?.title ?? localize('installExtensions', "Install Extensions"),
78-
undefined,
79-
toolInvocation.confirmationMessages?.message ?? localize('installExtensionsConfirmation', "Click the Install button on the extension and then press Continue when finished."),
80-
buttons,
8177
context.container,
78+
{
79+
title: toolInvocation.confirmationMessages?.title ?? localize('installExtensions', "Install Extensions"),
80+
message: toolInvocation.confirmationMessages?.message ?? localize('installExtensionsConfirmation', "Click the Install button on the extension and then press Continue when finished."),
81+
buttons,
82+
}
8283
));
8384
this._register(confirmWidget.onDidChangeHeight(() => this._onDidChangeHeight.fire()));
8485
dom.append(this.domNode, confirmWidget.domNode);

src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolSubPart.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,8 @@ export class TerminalConfirmationWidgetSubPart extends BaseChatToolInvocationSub
153153
dom.append(element, renderedMessage.element);
154154
const confirmWidget = this._register(this.instantiationService.createInstance(
155155
ChatCustomConfirmationWidget,
156-
title,
157-
undefined,
158-
element,
159-
buttons,
160156
this.context.container,
157+
{ title, message: element, buttons },
161158
));
162159

163160
if (disclaimer) {

src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolConfirmationSubPart.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,15 +102,13 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart {
102102
isSecondary: true,
103103
tooltip: cancelTooltip
104104
}];
105+
105106
let confirmWidget: ChatConfirmationWidget | ChatCustomConfirmationWidget;
106107
if (typeof message === 'string') {
107108
confirmWidget = this._register(this.instantiationService.createInstance(
108109
ChatConfirmationWidget,
109-
title,
110-
toolInvocation.originMessage,
111-
message,
112-
buttons,
113110
this.context.container,
111+
{ title, subtitle: toolInvocation.originMessage, buttons, message, toolbarData: { arg: toolInvocation, partType: 'chatToolConfirmation' } }
114112
));
115113
} else {
116114
const codeBlockRenderOptions: ICodeBlockRenderOptions = {
@@ -275,11 +273,8 @@ export class ToolConfirmationSubPart extends BaseChatToolInvocationSubPart {
275273

276274
confirmWidget = this._register(this.instantiationService.createInstance(
277275
ChatCustomConfirmationWidget,
278-
title,
279-
toolInvocation.originMessage,
280-
elements.root,
281-
buttons,
282276
this.context.container,
277+
{ title, subtitle: toolInvocation.originMessage, buttons, message: elements.root, toolbarData: { arg: toolInvocation, partType: 'chatToolConfirmation' } },
283278
));
284279
}
285280

src/vs/workbench/contrib/chat/browser/chatElicitationRequestPart.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ export class ChatElicitationRequestPart extends Disposable implements IChatElici
1919
constructor(
2020
public readonly title: string | IMarkdownString,
2121
public readonly message: string | IMarkdownString,
22-
public readonly originMessage: string | IMarkdownString,
22+
public readonly subtitle: string | IMarkdownString,
2323
public readonly acceptButtonLabel: string,
2424
public readonly rejectButtonLabel: string,
2525
public readonly accept: () => Promise<void>,
2626
public readonly reject: () => Promise<void>,
27+
public readonly source?: { type: 'mcp'; definitionId: string },
2728
) {
2829
super();
2930
}

src/vs/workbench/contrib/chat/common/chatService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ export interface IChatElicitationRequest {
262262
message: string | IMarkdownString;
263263
acceptButtonLabel: string;
264264
rejectButtonLabel: string;
265-
originMessage?: string | IMarkdownString;
265+
subtitle?: string | IMarkdownString;
266+
source?: { type: 'mcp'; definitionId: string };
266267
state: 'pending' | 'accepted' | 'rejected';
267268
acceptedResult?: Record<string, unknown>;
268269
accept(): Promise<void>;

0 commit comments

Comments
 (0)