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