mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-25 18:46:06 +01:00
Automations: Bring workspace picker under the chat input (#325397)
* Debt: Remove workspacePickerInput from chatInputPart * Remove dead renderTrigger code * Cleanup: Consolidate into a single pickeractionviewitem class * signing commit
This commit is contained in:
@@ -94,6 +94,7 @@ interface IRenderFormHandle {
|
||||
|
||||
|
||||
const AUTOMATIONS_HARNESS_CHIP_ACTION_ID = 'workbench.action.chat.renderAutomationsHarnessChip';
|
||||
const AUTOMATIONS_WORKSPACE_PICKER_ACTION_ID = 'workbench.action.chat.renderAutomationsWorkspacePicker';
|
||||
const AUTOMATIONS_ISOLATION_GROUP_ACTION_ID = 'workbench.action.chat.renderAutomationsIsolationGroup';
|
||||
|
||||
class AutomationIsolationGroupActionViewItem extends BaseActionViewItem {
|
||||
@@ -263,15 +264,15 @@ class AutomationIsolationGroupActionViewItem extends BaseActionViewItem {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hosts the shared {@link SessionTypePicker} inside the chat input's secondary
|
||||
* toolbar, in the slot previously occupied by the hardcoded harness chip. The
|
||||
* Renders a dialog-owned picker into a chat input secondary-toolbar slot. The
|
||||
* picker instance is owned by the dialog (registered on its disposables); this
|
||||
* view item only renders it into the toolbar container.
|
||||
* view item only injects the picker's DOM into the toolbar container via the
|
||||
* supplied {@link renderPicker} callback.
|
||||
*/
|
||||
class AutomationSessionTypePickerActionViewItem extends BaseActionViewItem {
|
||||
class AutomationPickerActionViewItem extends BaseActionViewItem {
|
||||
constructor(
|
||||
action: IAction,
|
||||
private readonly picker: MobileSessionTypePicker,
|
||||
private readonly renderPicker: (container: HTMLElement) => void,
|
||||
options?: IBaseActionViewItemOptions,
|
||||
) {
|
||||
super(undefined, action, options);
|
||||
@@ -280,7 +281,7 @@ class AutomationSessionTypePickerActionViewItem extends BaseActionViewItem {
|
||||
override render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
DOM.clearNode(container);
|
||||
this.picker.render(container);
|
||||
this.renderPicker(container);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,6 +304,25 @@ registerAction2(class OpenAutomationsHarnessChipAction extends Action2 {
|
||||
override async run(): Promise<void> { /* handled by action view item */ }
|
||||
});
|
||||
|
||||
registerAction2(class OpenAutomationsWorkspacePickerAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: AUTOMATIONS_WORKSPACE_PICKER_ACTION_ID,
|
||||
title: localize2('automation.form.workspacePicker.action', "Automations Workspace Picker"),
|
||||
f1: false,
|
||||
precondition: ChatContextKeys.enabled,
|
||||
menu: [{
|
||||
id: MenuId.ChatInputSecondary,
|
||||
group: 'navigation',
|
||||
order: 0,
|
||||
when: ChatContextKeys.inAutomationsDialog,
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
override async run(): Promise<void> { /* handled by action view item */ }
|
||||
});
|
||||
|
||||
registerAction2(class OpenAutomationsIsolationGroupAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -505,10 +525,15 @@ export function renderForm(
|
||||
// leaving its scrollbar floating ~24px in from the right wall.
|
||||
inputPartHorizontalPadding: 0,
|
||||
sessionTypePickerDelegate: sessionTypeDelegate,
|
||||
workspacePickerInput: workspacePicker,
|
||||
secondaryToolbarActionViewItemProvider: (action, itemOptions) => {
|
||||
if (action.id === AUTOMATIONS_HARNESS_CHIP_ACTION_ID) {
|
||||
return new AutomationSessionTypePickerActionViewItem(action, sessionTypePicker, itemOptions);
|
||||
return new AutomationPickerActionViewItem(action, container => sessionTypePicker.render(container), itemOptions);
|
||||
}
|
||||
if (action.id === AUTOMATIONS_WORKSPACE_PICKER_ACTION_ID) {
|
||||
return new AutomationPickerActionViewItem(action, container => {
|
||||
container.classList.add('chat-input-picker-item');
|
||||
workspacePicker.render(container);
|
||||
}, itemOptions);
|
||||
}
|
||||
if (action.id === AUTOMATIONS_ISOLATION_GROUP_ACTION_ID) {
|
||||
const actionWidgetService = instantiationService.invokeFunction(accessor => accessor.get(IActionWidgetService));
|
||||
|
||||
@@ -274,10 +274,8 @@ export class WorkspacePicker extends Disposable {
|
||||
* Renders the project picker trigger button into the given container.
|
||||
* Returns the container element.
|
||||
*
|
||||
* This is the single-trigger entry point. Calling it again replaces the
|
||||
* trigger created by the previous {@link render} call. For multi-trigger
|
||||
* use (e.g. mirroring the same picker into two surfaces) call
|
||||
* {@link renderTrigger} instead.
|
||||
* Calling it again replaces the trigger created by the previous
|
||||
* {@link render} call.
|
||||
*/
|
||||
render(container: HTMLElement): HTMLElement {
|
||||
this._renderDisposables.clear();
|
||||
@@ -290,26 +288,8 @@ export class WorkspacePicker extends Disposable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an additional trigger anchored to {@link container}. Unlike
|
||||
* {@link render}, calling this does NOT remove triggers from earlier
|
||||
* calls. Each trigger is independent and disposed via its own returned
|
||||
* disposable. All live triggers share this picker's selection state and
|
||||
* receive label updates from {@link _updateTriggerLabel}.
|
||||
*
|
||||
* Clicking any trigger anchors the popup to that specific trigger.
|
||||
*/
|
||||
renderTrigger(container: HTMLElement): IDisposable {
|
||||
const slot = dom.append(container, dom.$('.sessions-chat-picker-slot.sessions-chat-workspace-picker'));
|
||||
const triggerDisposables = new DisposableStore();
|
||||
triggerDisposables.add({ dispose: () => slot.remove() });
|
||||
triggerDisposables.add(this._addTrigger(slot));
|
||||
return triggerDisposables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared trigger-creation core for both {@link render} and
|
||||
* {@link renderTrigger}. Wires up the click / keyboard / touch handlers
|
||||
* and the per-trigger lifecycle.
|
||||
* Shared trigger-creation core for {@link render}. Wires up the click /
|
||||
* keyboard / touch handlers and the per-trigger lifecycle.
|
||||
*/
|
||||
private _addTrigger(slot: HTMLElement): IDisposable {
|
||||
const triggerDisposables = new DisposableStore();
|
||||
|
||||
@@ -666,41 +666,6 @@ export class OpenWorkspacePickerAction extends Action2 {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Workspace picker chip for the automations dialog. Sits between the mode
|
||||
* picker (order 1) and the model picker (order 3) in the primary chat input
|
||||
* toolbar. Visible only when the hosting `ChatInputPart` was constructed with
|
||||
* a `workspacePickerInput` and the dialog has set
|
||||
* {@link ChatContextKeys.inAutomationsDialog} on its scoped context-key
|
||||
* service.
|
||||
*/
|
||||
export class OpenAutomationsWorkspacePickerAction extends Action2 {
|
||||
static readonly ID = 'workbench.action.chat.openAutomationsWorkspacePicker';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: OpenAutomationsWorkspacePickerAction.ID,
|
||||
title: localize2('interactive.openAutomationsWorkspacePicker.label', "Open Automations Workspace Picker"),
|
||||
tooltip: localize('selectAutomationsWorkspace', "Select Workspace Folder"),
|
||||
category: CHAT_CATEGORY,
|
||||
f1: false,
|
||||
precondition: ChatContextKeys.enabled,
|
||||
menu: [
|
||||
{
|
||||
id: MenuId.ChatInput,
|
||||
order: 2,
|
||||
group: 'navigation',
|
||||
when: ChatContextKeys.inAutomationsDialog,
|
||||
},
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {
|
||||
// The picker is opened via the action view item's trigger.
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatSessionPrimaryPickerAction extends Action2 {
|
||||
static readonly ID = 'workbench.action.chat.chatSessionPrimaryPicker';
|
||||
constructor() {
|
||||
@@ -1267,7 +1232,6 @@ export function registerChatExecuteActions(): DisposableStore {
|
||||
store.add(registerAction2(OpenSessionTargetPickerAction));
|
||||
store.add(registerAction2(OpenDelegationPickerAction));
|
||||
store.add(registerAction2(OpenWorkspacePickerAction));
|
||||
store.add(registerAction2(OpenAutomationsWorkspacePickerAction));
|
||||
store.add(registerAction2(ChatSessionPrimaryPickerAction));
|
||||
store.add(registerAction2(ChangeChatModelAction));
|
||||
store.add(registerAction2(CancelEdit));
|
||||
|
||||
+8
-8
@@ -2529,14 +2529,14 @@ body:has(.automation-dialog) .monaco-menu-container {
|
||||
*
|
||||
* Result: match the 11px base, neutralize the welcome-flow oversizing,
|
||||
* keep the Mode/Model color (`var(--vscode-icon-foreground)`), match
|
||||
* the chip metrics from `.chat-input-toolbar .chat-input-picker-item
|
||||
* .action-label` (chat.css:1822), and hide the chevron (Mode/Model
|
||||
* don't render one).
|
||||
* the chip metrics from `.chat-secondary-toolbar .chat-input-picker-item
|
||||
* .action-label` (chat.css:1728), and hide the chevron (the neighboring
|
||||
* secondary-toolbar pickers don't render one).
|
||||
*
|
||||
* Scoped to `.automation-form-prompt-host` so the welcome view's
|
||||
* standalone workspace picker keeps its larger sizing.
|
||||
*/
|
||||
.automation-form-prompt-host .chat-input-toolbar .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label {
|
||||
.automation-form-prompt-host .chat-secondary-toolbar .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label {
|
||||
height: 16px;
|
||||
padding: 3px 6px;
|
||||
font-size: 11px;
|
||||
@@ -2544,22 +2544,22 @@ body:has(.automation-dialog) .monaco-menu-container {
|
||||
color: var(--vscode-icon-foreground);
|
||||
}
|
||||
|
||||
.automation-form-prompt-host .chat-input-toolbar .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .sessions-chat-dropdown-label {
|
||||
.automation-form-prompt-host .chat-secondary-toolbar .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .sessions-chat-dropdown-label {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.automation-form-prompt-host .chat-input-toolbar .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label > .codicon:not(.sessions-chat-dropdown-chevron) {
|
||||
.automation-form-prompt-host .chat-secondary-toolbar .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label > .codicon:not(.sessions-chat-dropdown-chevron) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mode and Model pickers in this toolbar don't render a dropdown
|
||||
* The neighboring secondary-toolbar pickers don't render a dropdown
|
||||
* chevron. They are recognized as chips by their padding + hover. Hide
|
||||
* the chevron that `_renderTriggerLabel` always appends so the
|
||||
* workspace chip reads the same way.
|
||||
*/
|
||||
.automation-form-prompt-host .chat-input-toolbar .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .sessions-chat-dropdown-chevron {
|
||||
.automation-form-prompt-host .chat-secondary-toolbar .sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .sessions-chat-dropdown-chevron {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,23 +39,6 @@ export interface IWorkspacePickerItem {
|
||||
readonly isFolder: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrow contract for a workspace picker hosted as a chip in
|
||||
* {@link ChatInputPart}'s primary toolbar. Implementers own all selection
|
||||
* state; the chip is purely a rendering / interaction surface.
|
||||
*
|
||||
* Used by the automations dialog so a single picker instance can drive both
|
||||
* the form-row trigger and a toolbar chip without duplicating state.
|
||||
*/
|
||||
export interface IChatInputWorkspacePicker {
|
||||
/**
|
||||
* Renders a trigger chip into the given container. The returned disposable
|
||||
* removes only this trigger; sibling triggers (e.g. the form-row trigger
|
||||
* in the automations dialog) keep working.
|
||||
*/
|
||||
renderTrigger(container: HTMLElement): IDisposable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate interface for the workspace picker.
|
||||
* Allows consumers to get and set the target workspace for chat submissions in empty window contexts.
|
||||
|
||||
@@ -101,7 +101,7 @@ import { IChatResponseViewModel, isResponseVM } from '../../../common/model/chat
|
||||
import { IChatAgentService } from '../../../common/participants/chatAgents.js';
|
||||
import { ILanguageModelToolsService } from '../../../common/tools/languageModelToolsService.js';
|
||||
import { ChatHistoryNavigator } from '../../../common/widget/chatWidgetHistoryService.js';
|
||||
import { ChatSessionPrimaryPickerAction, ChatSubmitAction, IChatExecuteActionContext, OpenAutomationsWorkspacePickerAction, OpenDelegationPickerAction, OpenModelPickerAction, OpenModePickerAction, OpenPermissionPickerAction, OpenSessionTargetPickerAction, OpenWorkspacePickerAction } from '../../actions/chatExecuteActions.js';
|
||||
import { ChatSessionPrimaryPickerAction, ChatSubmitAction, IChatExecuteActionContext, OpenDelegationPickerAction, OpenModelPickerAction, OpenModePickerAction, OpenPermissionPickerAction, OpenSessionTargetPickerAction, OpenWorkspacePickerAction } from '../../actions/chatExecuteActions.js';
|
||||
import { AgentSessionProviders, AgentSessionTarget, getAgentSessionProvider } from '../../agentSessions/agentSessions.js';
|
||||
import { IAgentSessionsService } from '../../agentSessions/agentSessionsService.js';
|
||||
import { ChatAttachmentModel } from '../../attachments/chatAttachmentModel.js';
|
||||
@@ -109,7 +109,7 @@ import { IChatAttachmentWidgetRegistry } from '../../attachments/chatAttachmentW
|
||||
import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, BrowserViewAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, PromptFileAttachmentWidget, PromptTextAttachmentWidget, SCMHistoryItemAttachmentWidget, SCMHistoryItemChangeAttachmentWidget, SCMHistoryItemChangeRangeAttachmentWidget, TerminalCommandAttachmentWidget, ToolSetOrToolItemAttachmentWidget } from '../../attachments/chatAttachmentWidgets.js';
|
||||
import { ChatImplicitContexts } from '../../attachments/chatImplicitContext.js';
|
||||
import { ImplicitContextAttachmentWidget } from '../../attachments/implicitContextAttachment.js';
|
||||
import { IChatWidget, IChatWidgetViewModelChangeEvent, ISessionTypePickerDelegate, isIChatResourceViewContext, isIChatViewViewContext, IWorkspacePickerDelegate, IChatInputWorkspacePicker } from '../../chat.js';
|
||||
import { IChatWidget, IChatWidgetViewModelChangeEvent, ISessionTypePickerDelegate, isIChatResourceViewContext, isIChatViewViewContext, IWorkspacePickerDelegate } from '../../chat.js';
|
||||
import { ChatEditingShowChangesAction, ViewPreviousEditsAction } from '../../chatEditing/chatEditingActions.js';
|
||||
import { resizeImage } from '../../chatImageUtils.js';
|
||||
import { ChatSessionPickerActionItem, IChatSessionPickerDelegate } from '../../chatSessions/chatSessionPickerActionItem.js';
|
||||
@@ -144,7 +144,6 @@ import { IModePickerDelegate, isModeConsideredBuiltIn, ModePickerActionItem } fr
|
||||
import { IPermissionPickerDelegate, PermissionPickerActionItem } from './permissionPickerActionItem.js';
|
||||
import { SessionTypePickerActionItem } from './sessionTargetPickerActionItem.js';
|
||||
import { WorkspacePickerActionItem } from './workspacePickerActionItem.js';
|
||||
import { WorkspacePickerInputActionItem } from './workspacePickerInputActionItem.js';
|
||||
import { ChatContextUsageWidget } from '../../widgetHosts/viewPane/chatContextUsageWidget.js';
|
||||
import { Target } from '../../../common/promptSyntax/promptTypes.js';
|
||||
import { findLast } from '../../../../../../base/common/arraysFind.js';
|
||||
@@ -195,15 +194,6 @@ export interface IChatInputPartOptions {
|
||||
* for their chat request. This is useful for empty window contexts.
|
||||
*/
|
||||
workspacePickerDelegate?: IWorkspacePickerDelegate;
|
||||
/**
|
||||
* Optional workspace picker rendered as a chip in the PRIMARY toolbar
|
||||
* (between the mode picker and the model picker). Used by the automations
|
||||
* dialog so its existing `WorkspacePicker` instance can drive both the
|
||||
* form-row trigger and a toolbar chip from a single source of truth.
|
||||
* Gated by {@link ChatContextKeys.inAutomationsDialog} on the menu side
|
||||
* and a defensive null-check in `actionViewItemProvider`.
|
||||
*/
|
||||
workspacePickerInput?: IChatInputWorkspacePicker;
|
||||
/**
|
||||
* Optional action view item provider for host-owned secondary toolbar
|
||||
* chips registered on {@link MenuId.ChatInputSecondary}. Used by the
|
||||
@@ -3435,17 +3425,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
} else if (action.id === OpenModePickerAction.ID && action instanceof MenuItemAction) {
|
||||
const delegate: IModePickerDelegate = this._createModePickerDelegate();
|
||||
return this.modeWidget = this.instantiationService.createInstance(ModePickerActionItem, action, delegate, pickerOptions);
|
||||
} else if (action.id === OpenAutomationsWorkspacePickerAction.ID && action instanceof MenuItemAction) {
|
||||
// Toolbar chip for an externally-owned workspace picker
|
||||
// (today: the automations dialog's single picker instance,
|
||||
// also rendered in the dialog form row). Defensive guard:
|
||||
// the menu's `when` clause should already gate visibility,
|
||||
// but if a host registers the menu contribution without
|
||||
// supplying the picker we hide rather than throw.
|
||||
if (this.options.workspacePickerInput) {
|
||||
return this.instantiationService.createInstance(WorkspacePickerInputActionItem, action, this.options.workspacePickerInput, undefined);
|
||||
}
|
||||
return new HiddenActionViewItem(action);
|
||||
} else if ((action.id === OpenSessionTargetPickerAction.ID || action.id === OpenDelegationPickerAction.ID) && action instanceof MenuItemAction) {
|
||||
// Use provided delegate if available, otherwise create default delegate
|
||||
const delegate: ISessionTypePickerDelegate = this.options.sessionTypePickerDelegate ?? {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BaseActionViewItem, IBaseActionViewItemOptions } from '../../../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||
import { MutableDisposable } from '../../../../../../base/common/lifecycle.js';
|
||||
import { MenuItemAction } from '../../../../../../platform/actions/common/actions.js';
|
||||
import { IChatInputWorkspacePicker } from '../../chat.js';
|
||||
|
||||
/**
|
||||
* Toolbar chip that delegates rendering to an externally-owned
|
||||
* {@link IChatInputWorkspacePicker}. The picker owns selection state and
|
||||
* popup behavior; this view item is a thin lifecycle wrapper so the trigger
|
||||
* participates in `MenuWorkbenchToolBar` responsive overflow, refresh, and
|
||||
* disposal.
|
||||
*
|
||||
* Used by the automations dialog: its single `AutomationsWorkspacePicker`
|
||||
* instance renders one trigger into the form row and another into the chat
|
||||
* input's primary toolbar via this view item.
|
||||
*/
|
||||
export class WorkspacePickerInputActionItem extends BaseActionViewItem {
|
||||
|
||||
private readonly _triggerDisposable = this._register(new MutableDisposable());
|
||||
|
||||
constructor(
|
||||
action: MenuItemAction,
|
||||
private readonly picker: IChatInputWorkspacePicker,
|
||||
options?: IBaseActionViewItemOptions,
|
||||
) {
|
||||
super(undefined, action, options);
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
// Add `chat-input-picker-item` so the container picks up the standard
|
||||
// chat input toolbar chip layout (height, padding, dividers) used by
|
||||
// the Mode and Model picker neighbors. Visual sizing of the inner
|
||||
// `.sessions-chat-picker-slot` label/icon is overridden in
|
||||
// `aiCustomizationManagement.css`. The default sessions-layer
|
||||
// styling targets a 18px welcome-flow chip, which is too big here.
|
||||
container.classList.add('chat-input-picker-item');
|
||||
this._triggerDisposable.value = this.picker.renderTrigger(container);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user