Коммит 10cf37de создал по автору Rob Lourens's avatar Rob Lourens
Просмотр файлов

Add per-chatMode actions and keybindings

владелец 29d5a6d4
......@@ -10,6 +10,7 @@ import { ActionListItemKind, IActionListDelegate, IActionListItem } from './acti
import { ThemeIcon } from '../../../base/common/themables.js';
import { Codicon } from '../../../base/common/codicons.js';
import { getActiveElement, isHTMLElement } from '../../../base/browser/dom.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
export interface IActionWidgetDropdownAction extends IAction {
category?: { label: string; order: number };
......@@ -40,6 +41,7 @@ export class ActionWidgetDropdown extends BaseDropdown {
container: HTMLElement,
private readonly _options: IActionWidgetDropdownOptions,
@IActionWidgetService private readonly actionWidgetService: IActionWidgetService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
) {
super(container, _options);
}
......@@ -93,6 +95,7 @@ export class ActionWidgetDropdown extends BaseDropdown {
disabled: false,
hideIcon: false,
label: action.label,
keybinding: this.keybindingService.lookupKeybinding(action.id)
});
}
}
......
......@@ -40,7 +40,7 @@ export class ActionWidgetDropdownActionViewItem extends BaseActionViewItem {
return this.renderLabel(this.element);
};
this.actionWidgetDropdown = this._register(new ActionWidgetDropdown(container, { ...this.actionWidgetOptions, labelRenderer }, this._actionWidgetService));
this.actionWidgetDropdown = this._register(new ActionWidgetDropdown(container, { ...this.actionWidgetOptions, labelRenderer }, this._actionWidgetService, this._keybindingService));
this._register(this.actionWidgetDropdown.onDidChangeVisibility(visible => {
this.element?.setAttribute('aria-expanded', `${visible}`);
}));
......
......@@ -604,7 +604,7 @@ interface IBaseAction2Options extends IAction2CommonOptions {
f1?: false;
}
interface ICommandPaletteOptions extends IAction2CommonOptions {
export interface ICommandPaletteOptions extends IAction2CommonOptions {
/**
* The title of the command that will be displayed in the command palette after the category.
......
......@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isAncestorOfActiveElement } from '../../../../../base/browser/dom.js';
import { toAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../../base/common/actions.js';
import { coalesce } from '../../../../../base/common/arrays.js';
import { Codicon } from '../../../../../base/common/codicons.js';
......@@ -21,7 +22,7 @@ import { SuggestController } from '../../../../../editor/contrib/suggest/browser
import { localize, localize2 } from '../../../../../nls.js';
import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js';
import { DropdownWithPrimaryActionViewItem } from '../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js';
import { Action2, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js';
import { Action2, ICommandPaletteOptions, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js';
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
import { IsLinuxContext, IsWindowsContext } from '../../../../../platform/contextkey/common/contextkeys.js';
......@@ -50,7 +51,7 @@ import { extractAgentAndCommand } from '../../common/chatParserTypes.js';
import { IChatDetail, IChatService } from '../../common/chatService.js';
import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js';
import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js';
import { ChatMode, validateChatMode } from '../../common/constants.js';
import { ChatConfiguration, ChatMode, modeToString, validateChatMode } from '../../common/constants.js';
import { CopilotUsageExtensionFeatureId } from '../../common/languageModelStats.js';
import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js';
import { ChatViewId, IChatWidget, IChatWidgetService, showChatView, showCopilotView } from '../chat.js';
......@@ -100,88 +101,140 @@ export interface IChatViewOpenRequestEntry {
const OPEN_CHAT_QUOTA_EXCEEDED_DIALOG = 'workbench.action.chat.openQuotaExceededDialog';
export function registerChatActions() {
registerAction2(class OpenChatGlobalAction extends Action2 {
abstract class OpenChatGlobalAction extends Action2 {
constructor(overrides: Pick<ICommandPaletteOptions, 'keybinding' | 'title' | 'id' | 'menu'>, private readonly mode?: ChatMode) {
super({
...overrides,
icon: Codicon.copilot,
f1: true,
category: CHAT_CATEGORY,
precondition: ChatContextKeys.Setup.hidden.negate(),
});
}
constructor() {
super({
id: CHAT_OPEN_ACTION_ID,
title: localize2('openChat', "Open Chat"),
icon: Codicon.copilot,
f1: true,
category: CHAT_CATEGORY,
precondition: ChatContextKeys.Setup.hidden.negate(),
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI,
mac: {
primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI
}
},
menu: [{
id: MenuId.ChatTitleBarMenu,
group: 'a_open',
order: 1
}]
});
}
override async run(accessor: ServicesAccessor, opts?: string | IChatViewOpenOptions): Promise<void> {
opts = typeof opts === 'string' ? { query: opts } : opts;
override async run(accessor: ServicesAccessor, opts?: string | IChatViewOpenOptions): Promise<void> {
opts = typeof opts === 'string' ? { query: opts } : opts;
const chatService = accessor.get(IChatService);
const widgetService = accessor.get(IChatWidgetService);
const toolsService = accessor.get(ILanguageModelToolsService);
const viewsService = accessor.get(IViewsService);
const hostService = accessor.get(IHostService);
const chatService = accessor.get(IChatService);
const toolsService = accessor.get(ILanguageModelToolsService);
const viewsService = accessor.get(IViewsService);
const hostService = accessor.get(IHostService);
const chatWidget = await showChatView(viewsService);
if (!chatWidget) {
return;
let chatWidget = widgetService.lastFocusedWidget;
// When this was invoked to switch to a mode via keybinding, and some chat widget is focused, use that one.
// Otherwise, open the view.
if (!this.mode || !chatWidget || !isAncestorOfActiveElement(chatWidget.domNode)) {
chatWidget = await showChatView(viewsService);
}
if (!chatWidget) {
return;
}
const mode = opts?.mode ?? this.mode;
if (mode && validateChatMode(mode)) {
chatWidget.input.setChatMode(mode);
}
if (opts?.previousRequests?.length && chatWidget.viewModel) {
for (const { request, response } of opts.previousRequests) {
chatService.addCompleteRequest(chatWidget.viewModel.sessionId, request, undefined, 0, { message: response });
}
if (opts?.mode && validateChatMode(opts.mode)) {
chatWidget.input.setChatMode(opts.mode);
}
if (opts?.attachScreenshot) {
const screenshot = await hostService.getScreenshot();
if (screenshot) {
chatWidget.attachmentModel.addContext(convertBufferToScreenshotVariable(screenshot));
}
if (opts?.previousRequests?.length && chatWidget.viewModel) {
for (const { request, response } of opts.previousRequests) {
chatService.addCompleteRequest(chatWidget.viewModel.sessionId, request, undefined, 0, { message: response });
}
}
if (opts?.query) {
if (opts.query.startsWith('@') && (chatWidget.input.currentMode === ChatMode.Agent || chatService.edits2Enabled)) {
chatWidget.input.setChatMode(ChatMode.Ask);
}
if (opts?.attachScreenshot) {
const screenshot = await hostService.getScreenshot();
if (screenshot) {
chatWidget.attachmentModel.addContext(convertBufferToScreenshotVariable(screenshot));
}
if (opts.isPartialQuery) {
chatWidget.setInput(opts.query);
} else {
await chatWidget.waitForReady();
chatWidget.acceptInput(opts.query);
}
if (opts?.query) {
if (opts.query.startsWith('@') && (chatWidget.input.currentMode === ChatMode.Agent || chatService.edits2Enabled)) {
chatWidget.input.setChatMode(ChatMode.Ask);
}
if (opts.isPartialQuery) {
chatWidget.setInput(opts.query);
} else {
await chatWidget.waitForReady();
chatWidget.acceptInput(opts.query);
}
if (opts?.toolIds && opts.toolIds.length > 0) {
for (const toolId of opts.toolIds) {
const tool = toolsService.getTool(toolId);
if (tool) {
chatWidget.attachmentModel.addContext({
id: tool.id,
name: tool.displayName,
fullName: tool.displayName,
value: undefined,
icon: ThemeIcon.isThemeIcon(tool.icon) ? tool.icon : undefined,
kind: 'tool'
});
}
}
if (opts?.toolIds && opts.toolIds.length > 0) {
for (const toolId of opts.toolIds) {
const tool = toolsService.getTool(toolId);
if (tool) {
chatWidget.attachmentModel.addContext({
id: tool.id,
name: tool.displayName,
fullName: tool.displayName,
value: undefined,
icon: ThemeIcon.isThemeIcon(tool.icon) ? tool.icon : undefined,
kind: 'tool'
});
}
}
chatWidget.focusInput();
}
}
class PrimaryOpenChatGlobalAction extends OpenChatGlobalAction {
constructor() {
super({
id: CHAT_OPEN_ACTION_ID,
title: localize2('openChat', "Open Chat"),
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI,
mac: {
primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI
}
}
},
menu: [{
id: MenuId.ChatTitleBarMenu,
group: 'a_open',
order: 1
}]
});
}
}
export function getOpenChatActionIdForMode(mode: ChatMode): string {
const modeStr = modeToString(mode);
return `workbench.action.chat.open${modeStr}`;
}
abstract class ModeOpenChatGlobalAction extends OpenChatGlobalAction {
constructor(mode: ChatMode, keybinding?: ICommandPaletteOptions['keybinding']) {
super({
id: getOpenChatActionIdForMode(mode),
title: localize2('openChatMode', "Open Chat ({0})", modeToString(mode)),
keybinding
}, mode);
}
}
chatWidget.focusInput();
export function registerChatActions() {
registerAction2(PrimaryOpenChatGlobalAction);
registerAction2(class extends ModeOpenChatGlobalAction {
constructor() { super(ChatMode.Ask); }
});
registerAction2(class extends ModeOpenChatGlobalAction {
constructor() {
super(ChatMode.Agent, {
when: ContextKeyExpr.has(`config.${ChatConfiguration.AgentEnabled}`),
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI,
linux: {
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI
}
},);
}
});
registerAction2(class extends ModeOpenChatGlobalAction {
constructor() { super(ChatMode.Edit); }
});
registerAction2(class ToggleChatAction extends Action2 {
constructor() {
......
......@@ -169,6 +169,7 @@ export interface IChatAcceptInputOptions {
}
export interface IChatWidget {
readonly domNode: HTMLElement;
readonly onDidChangeViewModel: Event<void>;
readonly onDidAcceptInput: Event<void>;
readonly onDidHide: Event<void>;
......
......@@ -154,6 +154,10 @@ export class ChatWidget extends Disposable implements IChatWidget {
private listContainer!: HTMLElement;
private container!: HTMLElement;
get domNode() {
return this.container;
}
private welcomeMessageContainer!: HTMLElement;
private readonly welcomePart: MutableDisposable<ChatViewWelcomePart> = this._register(new MutableDisposable());
......
......@@ -8,7 +8,6 @@ import { renderLabelWithIcons } from '../../../../../base/browser/ui/iconLabel/i
import { IAction } from '../../../../../base/common/actions.js';
import { Event } from '../../../../../base/common/event.js';
import { IDisposable } from '../../../../../base/common/lifecycle.js';
import { localize } from '../../../../../nls.js';
import { ActionWidgetDropdownActionViewItem } from '../../../../../platform/actions/browser/actionWidgetDropdownActionViewItem.js';
import { MenuItemAction } from '../../../../../platform/actions/common/actions.js';
import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js';
......@@ -16,7 +15,8 @@ import { IActionWidgetDropdownActionProvider, IActionWidgetDropdownOptions } fro
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
import { IChatAgentService } from '../../common/chatAgents.js';
import { ChatMode } from '../../common/constants.js';
import { ChatMode, modeToString } from '../../common/constants.js';
import { getOpenChatActionIdForMode } from '../actions/chatActions.js';
import { IToggleChatModeArgs } from '../actions/chatExecuteActions.js';
export interface IModePickerDelegate {
......@@ -35,8 +35,8 @@ export class ModePickerActionItem extends ActionWidgetDropdownActionViewItem {
) {
const makeAction = (mode: ChatMode): IAction => ({
...action,
id: mode,
label: this.modeToString(mode),
id: getOpenChatActionIdForMode(mode),
label: modeToString(mode),
class: undefined,
enabled: true,
checked: delegate.getMode() === mode,
......@@ -70,21 +70,9 @@ export class ModePickerActionItem extends ActionWidgetDropdownActionViewItem {
this._register(delegate.onDidChangeMode(() => this.renderLabel(this.element!)));
}
private modeToString(mode: ChatMode) {
switch (mode) {
case ChatMode.Agent:
return localize('chat.agentMode', "Agent");
case ChatMode.Edit:
return localize('chat.normalMode', "Edit");
case ChatMode.Ask:
default:
return localize('chat.askMode', "Ask");
}
}
protected override renderLabel(element: HTMLElement): IDisposable | null {
this.setAriaLabelAttributes(element);
const state = this.modeToString(this.delegate.getMode());
const state = modeToString(this.delegate.getMode());
dom.reset(element, dom.$('span.chat-model-label', undefined, state), ...renderLabelWithIcons(`$(chevron-down)`));
return null;
}
......
......@@ -16,6 +16,18 @@ export enum ChatMode {
Agent = 'agent'
}
export function modeToString(mode: ChatMode) {
switch (mode) {
case ChatMode.Agent:
return 'Agent';
case ChatMode.Edit:
return 'Edit';
case ChatMode.Ask:
default:
return 'Ask';
}
}
export function validateChatMode(mode: unknown): ChatMode | undefined {
switch (mode) {
case ChatMode.Ask:
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать