Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/promptSyntax/newPromptFileActions.ts
3296 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 { isEqual } from '../../../../../base/common/resources.js';
7
import { URI } from '../../../../../base/common/uri.js';
8
import { getCodeEditor } from '../../../../../editor/browser/editorBrowser.js';
9
import { SnippetController2 } from '../../../../../editor/contrib/snippet/browser/snippetController2.js';
10
import { localize, localize2 } from '../../../../../nls.js';
11
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
12
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
13
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
14
import { IFileService } from '../../../../../platform/files/common/files.js';
15
import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
16
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
17
import { ILogService } from '../../../../../platform/log/common/log.js';
18
import { INotificationService, NeverShowAgainScope, Severity } from '../../../../../platform/notification/common/notification.js';
19
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
20
import { PromptsConfig } from '../../common/promptSyntax/config/config.js';
21
import { getLanguageIdForPromptsType, PromptsType } from '../../common/promptSyntax/promptTypes.js';
22
import { IUserDataSyncEnablementService, SyncResource } from '../../../../../platform/userDataSync/common/userDataSync.js';
23
import { IEditorService } from '../../../../services/editor/common/editorService.js';
24
import { CONFIGURE_SYNC_COMMAND_ID } from '../../../../services/userDataSync/common/userDataSync.js';
25
import { ISnippetsService } from '../../../snippets/browser/snippets.js';
26
import { ChatContextKeys } from '../../common/chatContextKeys.js';
27
import { CHAT_CATEGORY } from '../actions/chatActions.js';
28
import { askForPromptFileName } from './pickers/askForPromptName.js';
29
import { askForPromptSourceFolder } from './pickers/askForPromptSourceFolder.js';
30
import { IChatModeService } from '../../common/chatModes.js';
31
32
33
class AbstractNewPromptFileAction extends Action2 {
34
35
constructor(id: string, title: string, private readonly type: PromptsType) {
36
super({
37
id,
38
title,
39
f1: false,
40
precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),
41
category: CHAT_CATEGORY,
42
keybinding: {
43
weight: KeybindingWeight.WorkbenchContrib
44
},
45
menu: {
46
id: MenuId.CommandPalette,
47
when: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled)
48
}
49
});
50
}
51
52
public override async run(accessor: ServicesAccessor) {
53
const logService = accessor.get(ILogService);
54
const openerService = accessor.get(IOpenerService);
55
const commandService = accessor.get(ICommandService);
56
const notificationService = accessor.get(INotificationService);
57
const userDataSyncEnablementService = accessor.get(IUserDataSyncEnablementService);
58
const editorService = accessor.get(IEditorService);
59
const fileService = accessor.get(IFileService);
60
const instaService = accessor.get(IInstantiationService);
61
const chatModeService = accessor.get(IChatModeService);
62
63
const selectedFolder = await instaService.invokeFunction(askForPromptSourceFolder, this.type);
64
if (!selectedFolder) {
65
return;
66
}
67
68
const fileName = await instaService.invokeFunction(askForPromptFileName, this.type, selectedFolder.uri);
69
if (!fileName) {
70
return;
71
}
72
73
// create the prompt file
74
75
await fileService.createFolder(selectedFolder.uri);
76
77
const promptUri = URI.joinPath(selectedFolder.uri, fileName);
78
await fileService.createFile(promptUri);
79
80
await openerService.open(promptUri);
81
82
const editor = getCodeEditor(editorService.activeTextEditorControl);
83
if (editor && editor.hasModel() && isEqual(editor.getModel().uri, promptUri)) {
84
SnippetController2.get(editor)?.apply([{
85
range: editor.getModel().getFullModelRange(),
86
template: this.getDefaultContentSnippet(this.type, chatModeService),
87
}]);
88
}
89
90
if (selectedFolder.storage !== 'user') {
91
return;
92
}
93
94
// due to PII concerns, synchronization of the 'user' reusable prompts
95
// is disabled by default, but we want to make that fact clear to the user
96
// hence after a 'user' prompt is create, we check if the synchronization
97
// was explicitly configured before, and if it wasn't, we show a suggestion
98
// to enable the synchronization logic in the Settings Sync configuration
99
100
const isConfigured = userDataSyncEnablementService
101
.isResourceEnablementConfigured(SyncResource.Prompts);
102
const isSettingsSyncEnabled = userDataSyncEnablementService.isEnabled();
103
104
// if prompts synchronization has already been configured before or
105
// if settings sync service is currently disabled, nothing to do
106
if ((isConfigured === true) || (isSettingsSyncEnabled === false)) {
107
return;
108
}
109
110
// show suggestion to enable synchronization of the user prompts and instructions to the user
111
notificationService.prompt(
112
Severity.Info,
113
localize(
114
'workbench.command.prompts.create.user.enable-sync-notification',
115
"Do you want to backup and sync your user prompt, instruction and mode files with Setting Sync?'",
116
),
117
[
118
{
119
label: localize('enable.capitalized', "Enable"),
120
run: () => {
121
commandService.executeCommand(CONFIGURE_SYNC_COMMAND_ID)
122
.catch((error) => {
123
logService.error(`Failed to run '${CONFIGURE_SYNC_COMMAND_ID}' command: ${error}.`);
124
});
125
},
126
},
127
{
128
label: localize('learnMore.capitalized', "Learn More"),
129
run: () => {
130
openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help'));
131
},
132
},
133
],
134
{
135
neverShowAgain: {
136
id: 'workbench.command.prompts.create.user.enable-sync-notification',
137
scope: NeverShowAgainScope.PROFILE,
138
},
139
},
140
);
141
}
142
143
private getDefaultContentSnippet(promptType: PromptsType, chatModeService: IChatModeService): string {
144
const modes = chatModeService.getModes();
145
const modeNames = modes.builtin.map(mode => mode.name).join(',') + (modes.custom.length ? (',' + modes.custom.map(mode => mode.name).join(',')) : '');
146
switch (promptType) {
147
case PromptsType.prompt:
148
return [
149
`---`,
150
`mode: \${1|${modeNames}|}`,
151
`---`,
152
`\${2:Define the task to achieve, including specific requirements, constraints, and success criteria.}`,
153
].join('\n');
154
case PromptsType.instructions:
155
return [
156
`---`,
157
`applyTo: '\${1|**,**/*.ts|}'`,
158
`---`,
159
`\${2:Provide project context and coding guidelines that AI should follow when generating code, answering questions, or reviewing changes.}`,
160
].join('\n');
161
case PromptsType.mode:
162
return [
163
`---`,
164
`description: '\${1:Description of the custom chat mode.}'`,
165
`tools: []`,
166
`---`,
167
`\${2:Define the purpose of this chat mode and how AI should behave: response style, available tools, focus areas, and any mode-specific instructions or constraints.}`,
168
].join('\n');
169
default:
170
throw new Error(`Unknown prompt type: ${promptType}`);
171
}
172
}
173
}
174
175
176
177
export const NEW_PROMPT_COMMAND_ID = 'workbench.command.new.prompt';
178
export const NEW_INSTRUCTIONS_COMMAND_ID = 'workbench.command.new.instructions';
179
export const NEW_MODE_COMMAND_ID = 'workbench.command.new.mode';
180
181
class NewPromptFileAction extends AbstractNewPromptFileAction {
182
constructor() {
183
super(NEW_PROMPT_COMMAND_ID, localize('commands.new.prompt.local.title', "New Prompt File..."), PromptsType.prompt);
184
}
185
}
186
187
class NewInstructionsFileAction extends AbstractNewPromptFileAction {
188
constructor() {
189
super(NEW_INSTRUCTIONS_COMMAND_ID, localize('commands.new.instructions.local.title', "New Instructions File..."), PromptsType.instructions);
190
}
191
}
192
193
class NewModeFileAction extends AbstractNewPromptFileAction {
194
constructor() {
195
super(NEW_MODE_COMMAND_ID, localize('commands.new.mode.local.title', "New Mode File..."), PromptsType.mode);
196
}
197
}
198
199
class NewUntitledPromptFileAction extends Action2 {
200
constructor() {
201
super({
202
id: 'workbench.command.new.untitled.prompt',
203
title: localize2('commands.new.untitled.prompt.title', "New Untitled Prompt File"),
204
f1: true,
205
precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),
206
category: CHAT_CATEGORY,
207
keybinding: {
208
weight: KeybindingWeight.WorkbenchContrib
209
},
210
});
211
}
212
213
public override async run(accessor: ServicesAccessor) {
214
const editorService = accessor.get(IEditorService);
215
const snippetService = accessor.get(ISnippetsService);
216
217
const languageId = getLanguageIdForPromptsType(PromptsType.prompt);
218
219
const input = await editorService.openEditor({
220
resource: undefined,
221
languageId,
222
options: {
223
pinned: true
224
}
225
});
226
227
const editor = getCodeEditor(editorService.activeTextEditorControl);
228
if (editor && editor.hasModel()) {
229
const snippets = await snippetService.getSnippets(languageId, { fileTemplateSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true });
230
if (snippets.length > 0) {
231
SnippetController2.get(editor)?.apply([{
232
range: editor.getModel().getFullModelRange(),
233
template: snippets[0].body
234
}]);
235
}
236
}
237
238
return input;
239
}
240
}
241
242
export function registerNewPromptFileActions(): void {
243
registerAction2(NewPromptFileAction);
244
registerAction2(NewInstructionsFileAction);
245
registerAction2(NewModeFileAction);
246
registerAction2(NewUntitledPromptFileAction);
247
}
248
249