Path: blob/main/src/vs/sessions/contrib/copilotChatSessions/browser/claudePermissionModePicker.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../../base/browser/dom.js';6import { Gesture, EventType as TouchEventType } from '../../../../base/browser/touch.js';7import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js';8import { Codicon } from '../../../../base/common/codicons.js';9import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';10import { ThemeIcon } from '../../../../base/common/themables.js';11import { localize } from '../../../../nls.js';12import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js';13import { ActionListItemKind, IActionListDelegate, IActionListItem, IActionListOptions } from '../../../../platform/actionWidget/browser/actionList.js';14import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';15import { ISessionsProvidersService } from '../../../services/sessions/browser/sessionsProvidersService.js';16import { CopilotChatSessionsProvider } from './copilotChatSessionsProvider.js';17import { IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';1819const PERMISSION_MODE_OPTION_ID = 'permissionMode';2021interface IClaudePermissionModeItem {22readonly id: string;23readonly label: string;24readonly description: string;25readonly icon: ThemeIcon;26}2728const permissionModes: IClaudePermissionModeItem[] = [29{30id: 'default',31label: localize('claude.permissionMode.default', "Ask Before Edits"),32description: localize('claude.permissionMode.default.description', "Claude asks for approval before making changes"),33icon: Codicon.shield,34},35{36id: 'acceptEdits',37label: localize('claude.permissionMode.acceptEdits', "Edit Automatically"),38description: localize('claude.permissionMode.acceptEdits.description', "Claude edits files without asking"),39icon: Codicon.edit,40},41{42id: 'plan',43label: localize('claude.permissionMode.plan', "Plan Mode"),44description: localize('claude.permissionMode.plan.description', "Claude creates a plan before making changes"),45icon: Codicon.lightbulb,46},47];4849export class ClaudePermissionModePicker extends Disposable {5051private _currentModeId = 'acceptEdits';52private _triggerElement: HTMLElement | undefined;53private readonly _renderDisposables = this._register(new DisposableStore());5455constructor(56@IActionWidgetService private readonly actionWidgetService: IActionWidgetService,57@ISessionsManagementService private readonly sessionsManagementService: ISessionsManagementService,58@ISessionsProvidersService private readonly sessionsProvidersService: ISessionsProvidersService,59@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,60) {61super();62}6364render(container: HTMLElement): HTMLElement {65this._renderDisposables.clear();6667const slot = dom.append(container, dom.$('.sessions-chat-picker-slot'));68this._renderDisposables.add({ dispose: () => slot.remove() });6970const trigger = dom.append(slot, dom.$('a.action-label'));71trigger.tabIndex = 0;72trigger.role = 'button';73this._triggerElement = trigger;7475this._updateTriggerLabel(trigger);7677this._renderDisposables.add(Gesture.addTarget(trigger));78for (const eventType of [dom.EventType.CLICK, TouchEventType.Tap]) {79this._renderDisposables.add(dom.addDisposableListener(trigger, eventType, (e) => {80dom.EventHelper.stop(e, true);81this._showPicker();82}));83}8485this._renderDisposables.add(dom.addDisposableListener(trigger, dom.EventType.KEY_DOWN, (e) => {86if (e.key === 'Enter' || e.key === ' ') {87dom.EventHelper.stop(e, true);88this._showPicker();89}90}));9192return slot;93}9495private _showPicker(): void {96if (!this._triggerElement || this.actionWidgetService.isVisible) {97return;98}99100const items: IActionListItem<IClaudePermissionModeItem>[] = permissionModes.map(mode => ({101kind: ActionListItemKind.Action,102group: { kind: ActionListItemKind.Header, title: '', icon: mode.icon },103item: mode,104label: mode.label,105detail: mode.description,106disabled: false,107}));108109const triggerElement = this._triggerElement;110const delegate: IActionListDelegate<IClaudePermissionModeItem> = {111onSelect: (item) => {112this.actionWidgetService.hide();113this._selectMode(item);114},115onHide: () => { triggerElement.focus(); },116};117118const listOptions: IActionListOptions = { minWidth: 255 };119this.actionWidgetService.show<IClaudePermissionModeItem>(120'claudePermissionModePicker',121false,122items,123delegate,124this._triggerElement,125undefined,126[],127{128getWidgetAriaLabel: () => localize('claudePermissionModePicker.ariaLabel', "Permission Mode"),129},130listOptions,131);132}133134private _selectMode(mode: IClaudePermissionModeItem): void {135this._currentModeId = mode.id;136this._updateTriggerLabel(this._triggerElement);137138const session = this.sessionsManagementService.activeSession.get();139if (!session) {140return;141}142const provider = this.sessionsProvidersService.getProvider(session.providerId);143if (provider instanceof CopilotChatSessionsProvider) {144const chatSession = provider.getSession(session.sessionId);145if (!chatSession) {146return;147}148const option = { id: mode.id, name: mode.label };149if (chatSession.setOption) {150chatSession.setOption(PERMISSION_MODE_OPTION_ID, option);151} else {152this.chatSessionsService.setSessionOption(chatSession.resource, PERMISSION_MODE_OPTION_ID, option);153}154}155}156157private _updateTriggerLabel(trigger: HTMLElement | undefined): void {158if (!trigger) {159return;160}161162dom.clearNode(trigger);163const currentMode = permissionModes.find(m => m.id === this._currentModeId) ?? permissionModes[1];164165dom.append(trigger, renderIcon(currentMode.icon));166const labelSpan = dom.append(trigger, dom.$('span.sessions-chat-dropdown-label'));167labelSpan.textContent = currentMode.label;168dom.append(trigger, renderIcon(Codicon.chevronDown));169170trigger.ariaLabel = localize('claudePermissionModePicker.triggerAriaLabel', "Pick Permission Mode, {0}", currentMode.label);171}172}173174175