promptarchitect 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,620 @@
1
+ /**
2
+ * PromptArchitect Refiner Panel
3
+ *
4
+ * A webview panel that provides an in-place prompt refinement experience.
5
+ * Users type their prompt, click Refine, and the text transforms before their eyes.
6
+ */
7
+
8
+ import * as vscode from 'vscode';
9
+ import { PromptArchitectAPI } from './api';
10
+ import { getWorkspaceContext, hasWorkspaceIndex, ensureWorkspaceIndexed } from './extension';
11
+
12
+ export class RefinerPanel {
13
+ public static currentPanel: RefinerPanel | undefined;
14
+ private readonly panel: vscode.WebviewPanel;
15
+ private readonly api: PromptArchitectAPI;
16
+ private disposables: vscode.Disposable[] = [];
17
+
18
+ private constructor(panel: vscode.WebviewPanel, api: PromptArchitectAPI) {
19
+ this.panel = panel;
20
+ this.api = api;
21
+
22
+ // Set initial HTML content
23
+ this.updateWebview();
24
+
25
+ // Handle messages from webview
26
+ this.panel.webview.onDidReceiveMessage(
27
+ async (message) => {
28
+ switch (message.command) {
29
+ case 'refine':
30
+ await this.handleRefine(message.text, message.mode);
31
+ break;
32
+ case 'copyToClipboard':
33
+ await vscode.env.clipboard.writeText(message.text);
34
+ vscode.window.showInformationMessage('✅ Copied to clipboard!');
35
+ break;
36
+ case 'sendToChat':
37
+ await vscode.env.clipboard.writeText(message.text);
38
+ // Try to open Copilot Chat
39
+ try {
40
+ await vscode.commands.executeCommand('workbench.panel.chat.view.copilot.focus');
41
+ } catch {
42
+ // Fallback: try generic chat command
43
+ try {
44
+ await vscode.commands.executeCommand('workbench.action.chat.open');
45
+ } catch {
46
+ // If no chat available, just inform user
47
+ }
48
+ }
49
+ vscode.window.showInformationMessage('✅ Copied! Paste (Ctrl+V) in the chat input.');
50
+ break;
51
+ case 'insertInEditor':
52
+ await this.insertInActiveEditor(message.text);
53
+ break;
54
+ case 'indexWorkspace':
55
+ await vscode.commands.executeCommand('promptarchitect.indexWorkspace');
56
+ // Refresh the webview to update index status
57
+ this.updateWebview();
58
+ break;
59
+ }
60
+ },
61
+ null,
62
+ this.disposables
63
+ );
64
+
65
+ // Clean up on panel close
66
+ this.panel.onDidDispose(() => this.dispose(), null, this.disposables);
67
+ }
68
+
69
+ public static createOrShow(api: PromptArchitectAPI) {
70
+ const column = vscode.ViewColumn.Beside;
71
+
72
+ // If panel exists, show it
73
+ if (RefinerPanel.currentPanel) {
74
+ RefinerPanel.currentPanel.panel.reveal(column);
75
+ return;
76
+ }
77
+
78
+ // Create new panel
79
+ const panel = vscode.window.createWebviewPanel(
80
+ 'promptArchitectRefiner',
81
+ '✨ Prompt Refiner',
82
+ column,
83
+ {
84
+ enableScripts: true,
85
+ retainContextWhenHidden: true,
86
+ }
87
+ );
88
+
89
+ RefinerPanel.currentPanel = new RefinerPanel(panel, api);
90
+ }
91
+
92
+ public static prefill(text: string) {
93
+ if (RefinerPanel.currentPanel) {
94
+ RefinerPanel.currentPanel.panel.webview.postMessage({
95
+ command: 'prefill',
96
+ text,
97
+ });
98
+ }
99
+ }
100
+
101
+ private async handleRefine(text: string, mode: string) {
102
+ if (!text.trim()) {
103
+ this.panel.webview.postMessage({
104
+ command: 'error',
105
+ message: 'Please enter some text to refine.',
106
+ });
107
+ return;
108
+ }
109
+
110
+ // Send loading state
111
+ this.panel.webview.postMessage({ command: 'loading', loading: true });
112
+
113
+ // Automatically ensure workspace is indexed (no user interaction)
114
+ // This runs silently in the background
115
+ await ensureWorkspaceIndexed();
116
+
117
+ try {
118
+ const config = vscode.workspace.getConfiguration('promptarchitect');
119
+ const targetModel = config.get<string>('targetModel') || 'general';
120
+ const useWorkspaceContext = config.get<boolean>('useWorkspaceContext') !== false;
121
+
122
+ // Get feedback based on refinement mode
123
+ const feedbackMap: Record<string, string> = {
124
+ clarity: 'Make this prompt clearer and more specific. Improve structure and reduce ambiguity.',
125
+ detailed: 'Expand this prompt with more context, requirements, and specific details.',
126
+ concise: 'Make this prompt more concise while keeping the core intent. Remove redundancy.',
127
+ technical: 'Make this prompt more technically precise with proper terminology and specifications.',
128
+ creative: 'Make this prompt more engaging and creative while maintaining clarity.',
129
+ structured: 'Add clear structure with sections, bullet points, or numbered steps.',
130
+ };
131
+
132
+ let feedback = feedbackMap[mode] || feedbackMap.clarity;
133
+
134
+ // Append workspace context if available
135
+ if (useWorkspaceContext) {
136
+ const workspaceContext = getWorkspaceContext();
137
+ if (workspaceContext) {
138
+ feedback += `\n\n--- WORKSPACE CONTEXT ---\n${workspaceContext}\n--- END WORKSPACE CONTEXT ---\n\nUse the workspace context above to make the prompt more relevant to this specific project, using appropriate terminology, patterns, and conventions.`;
139
+ }
140
+ }
141
+
142
+ const result = await this.api.refinePrompt({
143
+ prompt: text,
144
+ feedback,
145
+ targetModel,
146
+ preserveStructure: mode === 'structured',
147
+ });
148
+
149
+ // Send refined text back to webview
150
+ this.panel.webview.postMessage({
151
+ command: 'refined',
152
+ text: result.refinedPrompt,
153
+ changes: result.changes,
154
+ });
155
+ } catch (error) {
156
+ this.panel.webview.postMessage({
157
+ command: 'error',
158
+ message: `Refinement failed: ${error}`,
159
+ });
160
+ } finally {
161
+ this.panel.webview.postMessage({ command: 'loading', loading: false });
162
+ }
163
+ }
164
+
165
+ private async insertInActiveEditor(text: string) {
166
+ const editor = vscode.window.activeTextEditor;
167
+ if (editor) {
168
+ await editor.edit((editBuilder) => {
169
+ if (editor.selection.isEmpty) {
170
+ editBuilder.insert(editor.selection.active, text);
171
+ } else {
172
+ editBuilder.replace(editor.selection, text);
173
+ }
174
+ });
175
+ vscode.window.showInformationMessage('✅ Inserted into editor!');
176
+ } else {
177
+ // Create new document with the text
178
+ const doc = await vscode.workspace.openTextDocument({ content: text });
179
+ await vscode.window.showTextDocument(doc);
180
+ }
181
+ }
182
+
183
+ private updateWebview() {
184
+ const isIndexed = hasWorkspaceIndex();
185
+ this.panel.webview.html = this.getHtmlContent(isIndexed);
186
+ }
187
+
188
+ private getHtmlContent(isIndexed: boolean): string {
189
+ const indexStatus = isIndexed
190
+ ? '<span class="index-status indexed">📁 Workspace indexed</span>'
191
+ : '<span class="index-status not-indexed" title="Click to index workspace for better refinement">⚠️ Not indexed</span>';
192
+
193
+ return /*html*/ `
194
+ <!DOCTYPE html>
195
+ <html lang="en">
196
+ <head>
197
+ <meta charset="UTF-8">
198
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
199
+ <title>Prompt Refiner</title>
200
+ <style>
201
+ * {
202
+ box-sizing: border-box;
203
+ margin: 0;
204
+ padding: 0;
205
+ }
206
+
207
+ body {
208
+ font-family: var(--vscode-font-family);
209
+ background: var(--vscode-editor-background);
210
+ color: var(--vscode-editor-foreground);
211
+ padding: 16px;
212
+ height: 100vh;
213
+ display: flex;
214
+ flex-direction: column;
215
+ }
216
+
217
+ .header {
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 8px;
221
+ margin-bottom: 16px;
222
+ padding-bottom: 12px;
223
+ border-bottom: 1px solid var(--vscode-widget-border);
224
+ }
225
+
226
+ .header h1 {
227
+ font-size: 16px;
228
+ font-weight: 600;
229
+ }
230
+
231
+ .header .sparkle {
232
+ font-size: 20px;
233
+ }
234
+
235
+ .input-container {
236
+ flex: 1;
237
+ display: flex;
238
+ flex-direction: column;
239
+ min-height: 0;
240
+ }
241
+
242
+ .prompt-input {
243
+ flex: 1;
244
+ width: 100%;
245
+ padding: 12px;
246
+ border: 1px solid var(--vscode-input-border);
247
+ background: var(--vscode-input-background);
248
+ color: var(--vscode-input-foreground);
249
+ border-radius: 6px;
250
+ font-family: var(--vscode-editor-font-family);
251
+ font-size: 13px;
252
+ line-height: 1.5;
253
+ resize: none;
254
+ min-height: 150px;
255
+ }
256
+
257
+ .prompt-input:focus {
258
+ outline: none;
259
+ border-color: var(--vscode-focusBorder);
260
+ }
261
+
262
+ .prompt-input::placeholder {
263
+ color: var(--vscode-input-placeholderForeground);
264
+ }
265
+
266
+ .mode-selector {
267
+ display: flex;
268
+ gap: 6px;
269
+ margin: 12px 0;
270
+ flex-wrap: wrap;
271
+ }
272
+
273
+ .mode-btn {
274
+ padding: 6px 12px;
275
+ border: 1px solid var(--vscode-button-secondaryBackground);
276
+ background: var(--vscode-button-secondaryBackground);
277
+ color: var(--vscode-button-secondaryForeground);
278
+ border-radius: 16px;
279
+ font-size: 11px;
280
+ cursor: pointer;
281
+ transition: all 0.15s ease;
282
+ }
283
+
284
+ .mode-btn:hover {
285
+ background: var(--vscode-button-secondaryHoverBackground);
286
+ }
287
+
288
+ .mode-btn.active {
289
+ background: var(--vscode-button-background);
290
+ color: var(--vscode-button-foreground);
291
+ border-color: var(--vscode-button-background);
292
+ }
293
+
294
+ .actions {
295
+ display: flex;
296
+ gap: 8px;
297
+ margin-top: 12px;
298
+ }
299
+
300
+ .btn {
301
+ flex: 1;
302
+ padding: 10px 16px;
303
+ border: none;
304
+ border-radius: 6px;
305
+ font-size: 13px;
306
+ font-weight: 500;
307
+ cursor: pointer;
308
+ display: flex;
309
+ align-items: center;
310
+ justify-content: center;
311
+ gap: 6px;
312
+ transition: all 0.15s ease;
313
+ }
314
+
315
+ .btn-primary {
316
+ background: var(--vscode-button-background);
317
+ color: var(--vscode-button-foreground);
318
+ }
319
+
320
+ .btn-primary:hover {
321
+ background: var(--vscode-button-hoverBackground);
322
+ }
323
+
324
+ .btn-secondary {
325
+ background: var(--vscode-button-secondaryBackground);
326
+ color: var(--vscode-button-secondaryForeground);
327
+ }
328
+
329
+ .btn-secondary:hover {
330
+ background: var(--vscode-button-secondaryHoverBackground);
331
+ }
332
+
333
+ .btn:disabled {
334
+ opacity: 0.6;
335
+ cursor: not-allowed;
336
+ }
337
+
338
+ .btn .icon {
339
+ font-size: 14px;
340
+ }
341
+
342
+ .status {
343
+ margin-top: 12px;
344
+ padding: 10px;
345
+ border-radius: 6px;
346
+ font-size: 12px;
347
+ display: none;
348
+ }
349
+
350
+ .status.visible {
351
+ display: block;
352
+ }
353
+
354
+ .status.loading {
355
+ background: var(--vscode-inputValidation-infoBackground);
356
+ border: 1px solid var(--vscode-inputValidation-infoBorder);
357
+ }
358
+
359
+ .status.success {
360
+ background: var(--vscode-inputValidation-infoBackground);
361
+ border: 1px solid var(--vscode-charts-green);
362
+ }
363
+
364
+ .status.error {
365
+ background: var(--vscode-inputValidation-errorBackground);
366
+ border: 1px solid var(--vscode-inputValidation-errorBorder);
367
+ }
368
+
369
+ .changes-list {
370
+ margin-top: 8px;
371
+ padding-left: 16px;
372
+ }
373
+
374
+ .changes-list li {
375
+ margin: 4px 0;
376
+ font-size: 11px;
377
+ color: var(--vscode-descriptionForeground);
378
+ }
379
+
380
+ .spinner {
381
+ display: inline-block;
382
+ width: 14px;
383
+ height: 14px;
384
+ border: 2px solid var(--vscode-button-foreground);
385
+ border-top-color: transparent;
386
+ border-radius: 50%;
387
+ animation: spin 0.8s linear infinite;
388
+ }
389
+
390
+ @keyframes spin {
391
+ to { transform: rotate(360deg); }
392
+ }
393
+
394
+ .char-count {
395
+ font-size: 11px;
396
+ color: var(--vscode-descriptionForeground);
397
+ text-align: right;
398
+ margin-top: 4px;
399
+ }
400
+
401
+ .tip {
402
+ font-size: 11px;
403
+ color: var(--vscode-descriptionForeground);
404
+ margin-top: 8px;
405
+ padding: 8px;
406
+ background: var(--vscode-textBlockQuote-background);
407
+ border-radius: 4px;
408
+ border-left: 3px solid var(--vscode-textLink-foreground);
409
+ }
410
+
411
+ .index-status {
412
+ font-size: 11px;
413
+ padding: 4px 8px;
414
+ border-radius: 12px;
415
+ cursor: pointer;
416
+ }
417
+
418
+ .index-status.indexed {
419
+ background: var(--vscode-testing-iconPassed);
420
+ color: var(--vscode-editor-background);
421
+ }
422
+
423
+ .index-status.not-indexed {
424
+ background: var(--vscode-inputValidation-warningBackground);
425
+ border: 1px solid var(--vscode-inputValidation-warningBorder);
426
+ }
427
+
428
+ .index-status:hover {
429
+ opacity: 0.8;
430
+ }
431
+ </style>
432
+ </head>
433
+ <body>
434
+ <div class="header">
435
+ <span class="sparkle">✨</span>
436
+ <h1>Prompt Refiner</h1>
437
+ ${indexStatus}
438
+ </div>
439
+
440
+ <div class="input-container">
441
+ <textarea
442
+ class="prompt-input"
443
+ id="promptInput"
444
+ placeholder="Type or paste your prompt here...&#10;&#10;Then click 'Refine' to improve it before sending to your AI chat."
445
+ ></textarea>
446
+ <div class="char-count"><span id="charCount">0</span> characters</div>
447
+ </div>
448
+
449
+ <div class="mode-selector">
450
+ <button class="mode-btn active" data-mode="clarity" title="Improve clarity and specificity">🎯 Clarity</button>
451
+ <button class="mode-btn" data-mode="detailed" title="Add more context and details">📝 Detailed</button>
452
+ <button class="mode-btn" data-mode="concise" title="Make more concise">✂️ Concise</button>
453
+ <button class="mode-btn" data-mode="technical" title="Add technical precision">⚙️ Technical</button>
454
+ <button class="mode-btn" data-mode="structured" title="Add structure and formatting">📋 Structured</button>
455
+ </div>
456
+
457
+ <div class="actions">
458
+ <button class="btn btn-primary" id="refineBtn">
459
+ <span class="icon">✨</span>
460
+ <span class="btn-text">Refine</span>
461
+ </button>
462
+ <button class="btn btn-secondary" id="copyBtn">
463
+ <span class="icon">📋</span>
464
+ Copy
465
+ </button>
466
+ <button class="btn btn-secondary" id="sendBtn" title="Copy to clipboard and open chat">
467
+ <span class="icon">💬</span>
468
+ Send to Chat
469
+ </button>
470
+ </div>
471
+
472
+ <div class="status" id="status"></div>
473
+
474
+ <div class="tip">
475
+ 💡 <strong>Tip:</strong> After refining, click "Send to Chat" to copy and open Copilot Chat, then paste with Ctrl+V.
476
+ </div>
477
+
478
+ <script>
479
+ const vscode = acquireVsCodeApi();
480
+ const input = document.getElementById('promptInput');
481
+ const charCount = document.getElementById('charCount');
482
+ const refineBtn = document.getElementById('refineBtn');
483
+ const copyBtn = document.getElementById('copyBtn');
484
+ const sendBtn = document.getElementById('sendBtn');
485
+ const status = document.getElementById('status');
486
+ const modeBtns = document.querySelectorAll('.mode-btn');
487
+ const indexStatus = document.querySelector('.index-status');
488
+
489
+ let currentMode = 'clarity';
490
+ let isLoading = false;
491
+
492
+ // Index status click handler
493
+ if (indexStatus) {
494
+ indexStatus.addEventListener('click', () => {
495
+ vscode.postMessage({ command: 'indexWorkspace' });
496
+ });
497
+ }
498
+
499
+ // Update character count
500
+ input.addEventListener('input', () => {
501
+ charCount.textContent = input.value.length;
502
+ });
503
+
504
+ // Mode selection
505
+ modeBtns.forEach(btn => {
506
+ btn.addEventListener('click', () => {
507
+ modeBtns.forEach(b => b.classList.remove('active'));
508
+ btn.classList.add('active');
509
+ currentMode = btn.dataset.mode;
510
+ });
511
+ });
512
+
513
+ // Refine button
514
+ refineBtn.addEventListener('click', () => {
515
+ if (isLoading) return;
516
+ vscode.postMessage({
517
+ command: 'refine',
518
+ text: input.value,
519
+ mode: currentMode
520
+ });
521
+ });
522
+
523
+ // Copy button
524
+ copyBtn.addEventListener('click', () => {
525
+ vscode.postMessage({
526
+ command: 'copyToClipboard',
527
+ text: input.value
528
+ });
529
+ });
530
+
531
+ // Send to chat button
532
+ sendBtn.addEventListener('click', () => {
533
+ vscode.postMessage({
534
+ command: 'sendToChat',
535
+ text: input.value
536
+ });
537
+ });
538
+
539
+ // Handle keyboard shortcuts
540
+ input.addEventListener('keydown', (e) => {
541
+ // Ctrl+Enter to refine
542
+ if (e.ctrlKey && e.key === 'Enter') {
543
+ e.preventDefault();
544
+ refineBtn.click();
545
+ }
546
+ // Ctrl+Shift+Enter to send to chat
547
+ if (e.ctrlKey && e.shiftKey && e.key === 'Enter') {
548
+ e.preventDefault();
549
+ sendBtn.click();
550
+ }
551
+ });
552
+
553
+ // Handle messages from extension
554
+ window.addEventListener('message', event => {
555
+ const message = event.data;
556
+
557
+ switch (message.command) {
558
+ case 'prefill':
559
+ input.value = message.text;
560
+ charCount.textContent = message.text.length;
561
+ input.focus();
562
+ break;
563
+
564
+ case 'loading':
565
+ isLoading = message.loading;
566
+ refineBtn.disabled = message.loading;
567
+ if (message.loading) {
568
+ refineBtn.innerHTML = '<span class="spinner"></span><span class="btn-text">Refining...</span>';
569
+ status.className = 'status visible loading';
570
+ status.innerHTML = '⏳ Refining your prompt...';
571
+ } else {
572
+ refineBtn.innerHTML = '<span class="icon">✨</span><span class="btn-text">Refine</span>';
573
+ }
574
+ break;
575
+
576
+ case 'refined':
577
+ input.value = message.text;
578
+ charCount.textContent = message.text.length;
579
+ status.className = 'status visible success';
580
+ let html = '✅ Prompt refined successfully!';
581
+ if (message.changes && message.changes.length > 0) {
582
+ html += '<ul class="changes-list">';
583
+ message.changes.forEach(change => {
584
+ html += '<li>' + change + '</li>';
585
+ });
586
+ html += '</ul>';
587
+ }
588
+ status.innerHTML = html;
589
+ // Auto-hide after 5 seconds
590
+ setTimeout(() => {
591
+ status.className = 'status';
592
+ }, 5000);
593
+ break;
594
+
595
+ case 'error':
596
+ status.className = 'status visible error';
597
+ status.innerHTML = '❌ ' + message.message;
598
+ break;
599
+ }
600
+ });
601
+
602
+ // Focus input on load
603
+ input.focus();
604
+ </script>
605
+ </body>
606
+ </html>
607
+ `;
608
+ }
609
+
610
+ private dispose() {
611
+ RefinerPanel.currentPanel = undefined;
612
+ this.panel.dispose();
613
+ while (this.disposables.length) {
614
+ const disposable = this.disposables.pop();
615
+ if (disposable) {
616
+ disposable.dispose();
617
+ }
618
+ }
619
+ }
620
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Template Quick Pick Provider
3
+ */
4
+
5
+ import * as vscode from 'vscode';
6
+
7
+ export interface TemplateItem extends vscode.QuickPickItem {
8
+ value: string;
9
+ }
10
+
11
+ export const TEMPLATES: TemplateItem[] = [
12
+ {
13
+ label: '$(code) Coding',
14
+ value: 'coding',
15
+ description: 'Programming & development',
16
+ detail: 'Structured prompts for coding tasks with context, requirements, and constraints',
17
+ },
18
+ {
19
+ label: '$(book) Writing',
20
+ value: 'writing',
21
+ description: 'Content creation',
22
+ detail: 'Prompts optimized for writing with tone, audience, and style guidance',
23
+ },
24
+ {
25
+ label: '$(search) Research',
26
+ value: 'research',
27
+ description: 'Investigation & discovery',
28
+ detail: 'Research prompts with sources, methodology, and output format',
29
+ },
30
+ {
31
+ label: '$(graph) Analysis',
32
+ value: 'analysis',
33
+ description: 'Data & business analysis',
34
+ detail: 'Analysis prompts with metrics, comparisons, and recommendations',
35
+ },
36
+ {
37
+ label: '$(verified) Fact Check',
38
+ value: 'factcheck',
39
+ description: 'Verification & validation',
40
+ detail: 'Fact-checking prompts with claims, sources, and confidence levels',
41
+ },
42
+ {
43
+ label: '$(globe) General',
44
+ value: 'general',
45
+ description: 'General-purpose',
46
+ detail: 'Versatile template for any task type',
47
+ },
48
+ ];
49
+
50
+ export class TemplateQuickPick {
51
+ static async show(): Promise<string | undefined> {
52
+ const selected = await vscode.window.showQuickPick(TEMPLATES, {
53
+ placeHolder: 'Select a template',
54
+ title: 'PromptArchitect - Choose Template',
55
+ matchOnDescription: true,
56
+ matchOnDetail: true,
57
+ });
58
+
59
+ return selected?.value;
60
+ }
61
+ }