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.
- package/.vscodeignore +7 -0
- package/CHANGELOG.md +28 -0
- package/LICENSE +44 -0
- package/README.md +200 -0
- package/docs/CHAT_UI_REDESIGN_PLAN.md +371 -0
- package/images/hub-icon.svg +6 -0
- package/images/prompt-lab-icon.svg +11 -0
- package/package.json +519 -0
- package/src/agentPrompts.ts +278 -0
- package/src/agentService.ts +630 -0
- package/src/api.ts +223 -0
- package/src/authService.ts +556 -0
- package/src/chatPanel.ts +979 -0
- package/src/extension.ts +822 -0
- package/src/providers/aiChatViewProvider.ts +1023 -0
- package/src/providers/environmentTreeProvider.ts +311 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/notesTreeProvider.ts +301 -0
- package/src/providers/quickAccessTreeProvider.ts +328 -0
- package/src/providers/scriptsTreeProvider.ts +324 -0
- package/src/refinerPanel.ts +620 -0
- package/src/templates.ts +61 -0
- package/src/workspaceIndexer.ts +766 -0
- package/tsconfig.json +16 -0
package/src/chatPanel.ts
ADDED
|
@@ -0,0 +1,979 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PromptArchitect Chat Panel
|
|
3
|
+
*
|
|
4
|
+
* A full-featured AI chat interface within VS Code.
|
|
5
|
+
* Users can refine prompts and chat with AI models directly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as vscode from 'vscode';
|
|
9
|
+
import { PromptArchitectAPI } from './api';
|
|
10
|
+
|
|
11
|
+
interface ChatMessage {
|
|
12
|
+
id: string;
|
|
13
|
+
role: 'user' | 'assistant' | 'system';
|
|
14
|
+
content: string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
refined?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class ChatPanel {
|
|
20
|
+
public static currentPanel: ChatPanel | undefined;
|
|
21
|
+
private readonly panel: vscode.WebviewPanel;
|
|
22
|
+
private readonly api: PromptArchitectAPI;
|
|
23
|
+
private disposables: vscode.Disposable[] = [];
|
|
24
|
+
private chatHistory: ChatMessage[] = [];
|
|
25
|
+
|
|
26
|
+
private constructor(panel: vscode.WebviewPanel, api: PromptArchitectAPI) {
|
|
27
|
+
this.panel = panel;
|
|
28
|
+
this.api = api;
|
|
29
|
+
|
|
30
|
+
// Set initial HTML content
|
|
31
|
+
this.updateWebview();
|
|
32
|
+
|
|
33
|
+
// Handle messages from webview
|
|
34
|
+
this.panel.webview.onDidReceiveMessage(
|
|
35
|
+
async (message) => {
|
|
36
|
+
switch (message.command) {
|
|
37
|
+
case 'sendMessage':
|
|
38
|
+
await this.handleSendMessage(message.text, message.refined);
|
|
39
|
+
break;
|
|
40
|
+
case 'refineBeforeSend':
|
|
41
|
+
await this.handleRefine(message.text, message.mode);
|
|
42
|
+
break;
|
|
43
|
+
case 'copyToClipboard':
|
|
44
|
+
await vscode.env.clipboard.writeText(message.text);
|
|
45
|
+
vscode.window.showInformationMessage('📋 Copied to clipboard!');
|
|
46
|
+
break;
|
|
47
|
+
case 'insertInEditor':
|
|
48
|
+
await this.insertInActiveEditor(message.text);
|
|
49
|
+
break;
|
|
50
|
+
case 'clearChat':
|
|
51
|
+
this.chatHistory = [];
|
|
52
|
+
this.syncChatHistory();
|
|
53
|
+
break;
|
|
54
|
+
case 'addContext':
|
|
55
|
+
await this.addEditorContext();
|
|
56
|
+
break;
|
|
57
|
+
case 'exportChat':
|
|
58
|
+
await this.exportChatHistory();
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
null,
|
|
63
|
+
this.disposables
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Clean up on panel close
|
|
67
|
+
this.panel.onDidDispose(() => this.dispose(), null, this.disposables);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public static createOrShow(api: PromptArchitectAPI) {
|
|
71
|
+
const column = vscode.ViewColumn.Beside;
|
|
72
|
+
|
|
73
|
+
// If panel exists, show it
|
|
74
|
+
if (ChatPanel.currentPanel) {
|
|
75
|
+
ChatPanel.currentPanel.panel.reveal(column);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Create new panel
|
|
80
|
+
const panel = vscode.window.createWebviewPanel(
|
|
81
|
+
'promptArchitectChat',
|
|
82
|
+
'💬 PromptArchitect Chat',
|
|
83
|
+
column,
|
|
84
|
+
{
|
|
85
|
+
enableScripts: true,
|
|
86
|
+
retainContextWhenHidden: true,
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
ChatPanel.currentPanel = new ChatPanel(panel, api);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public static prefillPrompt(text: string) {
|
|
94
|
+
if (ChatPanel.currentPanel) {
|
|
95
|
+
ChatPanel.currentPanel.panel.webview.postMessage({
|
|
96
|
+
command: 'prefill',
|
|
97
|
+
text,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private async handleSendMessage(text: string, wasRefined: boolean = false) {
|
|
103
|
+
if (!text.trim()) return;
|
|
104
|
+
|
|
105
|
+
// Add user message to history
|
|
106
|
+
const userMessage: ChatMessage = {
|
|
107
|
+
id: this.generateId(),
|
|
108
|
+
role: 'user',
|
|
109
|
+
content: text,
|
|
110
|
+
timestamp: Date.now(),
|
|
111
|
+
refined: wasRefined,
|
|
112
|
+
};
|
|
113
|
+
this.chatHistory.push(userMessage);
|
|
114
|
+
this.syncChatHistory();
|
|
115
|
+
|
|
116
|
+
// Show loading state
|
|
117
|
+
this.panel.webview.postMessage({ command: 'loading', loading: true });
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Call the chat API
|
|
121
|
+
const response = await this.api.chat({
|
|
122
|
+
message: text,
|
|
123
|
+
history: this.chatHistory.slice(0, -1).map(m => ({
|
|
124
|
+
role: m.role,
|
|
125
|
+
content: m.content,
|
|
126
|
+
})),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Add assistant response to history
|
|
130
|
+
const assistantMessage: ChatMessage = {
|
|
131
|
+
id: this.generateId(),
|
|
132
|
+
role: 'assistant',
|
|
133
|
+
content: response.reply,
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
};
|
|
136
|
+
this.chatHistory.push(assistantMessage);
|
|
137
|
+
this.syncChatHistory();
|
|
138
|
+
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// Add error message
|
|
141
|
+
const errorMessage: ChatMessage = {
|
|
142
|
+
id: this.generateId(),
|
|
143
|
+
role: 'assistant',
|
|
144
|
+
content: `⚠️ Error: ${error}. Please try again.`,
|
|
145
|
+
timestamp: Date.now(),
|
|
146
|
+
};
|
|
147
|
+
this.chatHistory.push(errorMessage);
|
|
148
|
+
this.syncChatHistory();
|
|
149
|
+
} finally {
|
|
150
|
+
this.panel.webview.postMessage({ command: 'loading', loading: false });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private async handleRefine(text: string, mode: string) {
|
|
155
|
+
if (!text.trim()) {
|
|
156
|
+
this.panel.webview.postMessage({
|
|
157
|
+
command: 'refineError',
|
|
158
|
+
message: 'Please enter some text to refine.',
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.panel.webview.postMessage({ command: 'refining', refining: true });
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const config = vscode.workspace.getConfiguration('promptarchitect');
|
|
167
|
+
const targetModel = config.get<string>('targetModel') || 'general';
|
|
168
|
+
|
|
169
|
+
const feedbackMap: Record<string, string> = {
|
|
170
|
+
clarity: 'Make this prompt clearer and more specific. Improve structure and reduce ambiguity.',
|
|
171
|
+
detailed: 'Expand this prompt with more context, requirements, and specific details.',
|
|
172
|
+
concise: 'Make this prompt more concise while keeping the core intent. Remove redundancy.',
|
|
173
|
+
technical: 'Make this prompt more technically precise with proper terminology.',
|
|
174
|
+
structured: 'Add clear structure with sections, bullet points, or numbered steps.',
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result = await this.api.refinePrompt({
|
|
178
|
+
prompt: text,
|
|
179
|
+
feedback: feedbackMap[mode] || feedbackMap.clarity,
|
|
180
|
+
targetModel,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
this.panel.webview.postMessage({
|
|
184
|
+
command: 'refined',
|
|
185
|
+
text: result.refinedPrompt,
|
|
186
|
+
});
|
|
187
|
+
} catch (error) {
|
|
188
|
+
this.panel.webview.postMessage({
|
|
189
|
+
command: 'refineError',
|
|
190
|
+
message: `Refinement failed: ${error}`,
|
|
191
|
+
});
|
|
192
|
+
} finally {
|
|
193
|
+
this.panel.webview.postMessage({ command: 'refining', refining: false });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private async insertInActiveEditor(text: string) {
|
|
198
|
+
const editor = vscode.window.activeTextEditor;
|
|
199
|
+
if (editor) {
|
|
200
|
+
await editor.edit((editBuilder) => {
|
|
201
|
+
if (editor.selection.isEmpty) {
|
|
202
|
+
editBuilder.insert(editor.selection.active, text);
|
|
203
|
+
} else {
|
|
204
|
+
editBuilder.replace(editor.selection, text);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
vscode.window.showInformationMessage('✅ Inserted into editor!');
|
|
208
|
+
} else {
|
|
209
|
+
const doc = await vscode.workspace.openTextDocument({ content: text });
|
|
210
|
+
await vscode.window.showTextDocument(doc);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private async addEditorContext() {
|
|
215
|
+
const editor = vscode.window.activeTextEditor;
|
|
216
|
+
if (!editor) {
|
|
217
|
+
vscode.window.showWarningMessage('No active editor to get context from.');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let contextText = '';
|
|
222
|
+
const selection = editor.selection;
|
|
223
|
+
|
|
224
|
+
if (!selection.isEmpty) {
|
|
225
|
+
contextText = editor.document.getText(selection);
|
|
226
|
+
} else {
|
|
227
|
+
// Get visible text or first 100 lines
|
|
228
|
+
const visibleRanges = editor.visibleRanges;
|
|
229
|
+
if (visibleRanges.length > 0) {
|
|
230
|
+
contextText = editor.document.getText(visibleRanges[0]);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (contextText) {
|
|
235
|
+
const fileName = editor.document.fileName.split(/[\\/]/).pop();
|
|
236
|
+
const language = editor.document.languageId;
|
|
237
|
+
|
|
238
|
+
this.panel.webview.postMessage({
|
|
239
|
+
command: 'addContext',
|
|
240
|
+
context: {
|
|
241
|
+
fileName,
|
|
242
|
+
language,
|
|
243
|
+
code: contextText,
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private async exportChatHistory() {
|
|
250
|
+
const content = this.chatHistory.map(msg => {
|
|
251
|
+
const time = new Date(msg.timestamp).toLocaleTimeString();
|
|
252
|
+
const role = msg.role === 'user' ? '👤 You' : '🤖 Assistant';
|
|
253
|
+
return `### ${role} (${time})${msg.refined ? ' [Refined]' : ''}\n\n${msg.content}\n`;
|
|
254
|
+
}).join('\n---\n\n');
|
|
255
|
+
|
|
256
|
+
const doc = await vscode.workspace.openTextDocument({
|
|
257
|
+
content: `# PromptArchitect Chat Export\n\n${content}`,
|
|
258
|
+
language: 'markdown',
|
|
259
|
+
});
|
|
260
|
+
await vscode.window.showTextDocument(doc);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private syncChatHistory() {
|
|
264
|
+
this.panel.webview.postMessage({
|
|
265
|
+
command: 'syncHistory',
|
|
266
|
+
history: this.chatHistory,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private generateId(): string {
|
|
271
|
+
return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private updateWebview() {
|
|
275
|
+
this.panel.webview.html = this.getHtmlContent();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private getHtmlContent(): string {
|
|
279
|
+
return /*html*/ `
|
|
280
|
+
<!DOCTYPE html>
|
|
281
|
+
<html lang="en">
|
|
282
|
+
<head>
|
|
283
|
+
<meta charset="UTF-8">
|
|
284
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
285
|
+
<title>PromptArchitect Chat</title>
|
|
286
|
+
<style>
|
|
287
|
+
* {
|
|
288
|
+
box-sizing: border-box;
|
|
289
|
+
margin: 0;
|
|
290
|
+
padding: 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
body {
|
|
294
|
+
font-family: var(--vscode-font-family);
|
|
295
|
+
background: var(--vscode-editor-background);
|
|
296
|
+
color: var(--vscode-editor-foreground);
|
|
297
|
+
height: 100vh;
|
|
298
|
+
display: flex;
|
|
299
|
+
flex-direction: column;
|
|
300
|
+
overflow: hidden;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* Header */
|
|
304
|
+
.header {
|
|
305
|
+
display: flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
justify-content: space-between;
|
|
308
|
+
padding: 12px 16px;
|
|
309
|
+
border-bottom: 1px solid var(--vscode-widget-border);
|
|
310
|
+
background: var(--vscode-sideBar-background);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.header-left {
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
gap: 8px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.header h1 {
|
|
320
|
+
font-size: 14px;
|
|
321
|
+
font-weight: 600;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.header-actions {
|
|
325
|
+
display: flex;
|
|
326
|
+
gap: 4px;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.icon-btn {
|
|
330
|
+
background: transparent;
|
|
331
|
+
border: none;
|
|
332
|
+
color: var(--vscode-foreground);
|
|
333
|
+
cursor: pointer;
|
|
334
|
+
padding: 6px;
|
|
335
|
+
border-radius: 4px;
|
|
336
|
+
font-size: 14px;
|
|
337
|
+
opacity: 0.7;
|
|
338
|
+
transition: all 0.15s;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.icon-btn:hover {
|
|
342
|
+
opacity: 1;
|
|
343
|
+
background: var(--vscode-toolbar-hoverBackground);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.icon-btn[title]:hover::after {
|
|
347
|
+
content: attr(title);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* Chat Area */
|
|
351
|
+
.chat-container {
|
|
352
|
+
flex: 1;
|
|
353
|
+
overflow-y: auto;
|
|
354
|
+
padding: 16px;
|
|
355
|
+
display: flex;
|
|
356
|
+
flex-direction: column;
|
|
357
|
+
gap: 16px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.empty-state {
|
|
361
|
+
flex: 1;
|
|
362
|
+
display: flex;
|
|
363
|
+
flex-direction: column;
|
|
364
|
+
align-items: center;
|
|
365
|
+
justify-content: center;
|
|
366
|
+
text-align: center;
|
|
367
|
+
opacity: 0.7;
|
|
368
|
+
padding: 40px;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.empty-state .icon {
|
|
372
|
+
font-size: 48px;
|
|
373
|
+
margin-bottom: 16px;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.empty-state h2 {
|
|
377
|
+
font-size: 16px;
|
|
378
|
+
margin-bottom: 8px;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.empty-state p {
|
|
382
|
+
font-size: 12px;
|
|
383
|
+
color: var(--vscode-descriptionForeground);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.message {
|
|
387
|
+
display: flex;
|
|
388
|
+
gap: 12px;
|
|
389
|
+
animation: fadeIn 0.2s ease;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
@keyframes fadeIn {
|
|
393
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
394
|
+
to { opacity: 1; transform: translateY(0); }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.message.user {
|
|
398
|
+
flex-direction: row-reverse;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.avatar {
|
|
402
|
+
width: 32px;
|
|
403
|
+
height: 32px;
|
|
404
|
+
border-radius: 50%;
|
|
405
|
+
display: flex;
|
|
406
|
+
align-items: center;
|
|
407
|
+
justify-content: center;
|
|
408
|
+
font-size: 16px;
|
|
409
|
+
flex-shrink: 0;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.message.user .avatar {
|
|
413
|
+
background: var(--vscode-button-background);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.message.assistant .avatar {
|
|
417
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.message-content {
|
|
421
|
+
max-width: 80%;
|
|
422
|
+
padding: 12px 16px;
|
|
423
|
+
border-radius: 12px;
|
|
424
|
+
font-size: 13px;
|
|
425
|
+
line-height: 1.5;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.message.user .message-content {
|
|
429
|
+
background: var(--vscode-button-background);
|
|
430
|
+
color: var(--vscode-button-foreground);
|
|
431
|
+
border-bottom-right-radius: 4px;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.message.assistant .message-content {
|
|
435
|
+
background: var(--vscode-input-background);
|
|
436
|
+
border: 1px solid var(--vscode-widget-border);
|
|
437
|
+
border-bottom-left-radius: 4px;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.message-content pre {
|
|
441
|
+
background: var(--vscode-textCodeBlock-background);
|
|
442
|
+
padding: 12px;
|
|
443
|
+
border-radius: 6px;
|
|
444
|
+
overflow-x: auto;
|
|
445
|
+
margin: 8px 0;
|
|
446
|
+
font-family: var(--vscode-editor-font-family);
|
|
447
|
+
font-size: 12px;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.message-content code {
|
|
451
|
+
background: var(--vscode-textCodeBlock-background);
|
|
452
|
+
padding: 2px 6px;
|
|
453
|
+
border-radius: 4px;
|
|
454
|
+
font-family: var(--vscode-editor-font-family);
|
|
455
|
+
font-size: 12px;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.message-meta {
|
|
459
|
+
font-size: 10px;
|
|
460
|
+
color: var(--vscode-descriptionForeground);
|
|
461
|
+
margin-top: 4px;
|
|
462
|
+
display: flex;
|
|
463
|
+
gap: 8px;
|
|
464
|
+
align-items: center;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.refined-badge {
|
|
468
|
+
background: var(--vscode-badge-background);
|
|
469
|
+
color: var(--vscode-badge-foreground);
|
|
470
|
+
padding: 2px 6px;
|
|
471
|
+
border-radius: 10px;
|
|
472
|
+
font-size: 9px;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.message-actions {
|
|
476
|
+
display: flex;
|
|
477
|
+
gap: 4px;
|
|
478
|
+
margin-top: 8px;
|
|
479
|
+
opacity: 0;
|
|
480
|
+
transition: opacity 0.15s;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.message:hover .message-actions {
|
|
484
|
+
opacity: 1;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.msg-action-btn {
|
|
488
|
+
background: var(--vscode-button-secondaryBackground);
|
|
489
|
+
color: var(--vscode-button-secondaryForeground);
|
|
490
|
+
border: none;
|
|
491
|
+
padding: 4px 8px;
|
|
492
|
+
border-radius: 4px;
|
|
493
|
+
font-size: 11px;
|
|
494
|
+
cursor: pointer;
|
|
495
|
+
display: flex;
|
|
496
|
+
align-items: center;
|
|
497
|
+
gap: 4px;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.msg-action-btn:hover {
|
|
501
|
+
background: var(--vscode-button-secondaryHoverBackground);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/* Input Area */
|
|
505
|
+
.input-area {
|
|
506
|
+
border-top: 1px solid var(--vscode-widget-border);
|
|
507
|
+
padding: 16px;
|
|
508
|
+
background: var(--vscode-sideBar-background);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.refine-bar {
|
|
512
|
+
display: flex;
|
|
513
|
+
gap: 6px;
|
|
514
|
+
margin-bottom: 12px;
|
|
515
|
+
flex-wrap: wrap;
|
|
516
|
+
align-items: center;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.refine-label {
|
|
520
|
+
font-size: 11px;
|
|
521
|
+
color: var(--vscode-descriptionForeground);
|
|
522
|
+
margin-right: 4px;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.refine-btn {
|
|
526
|
+
padding: 4px 10px;
|
|
527
|
+
border: 1px solid var(--vscode-button-secondaryBackground);
|
|
528
|
+
background: var(--vscode-button-secondaryBackground);
|
|
529
|
+
color: var(--vscode-button-secondaryForeground);
|
|
530
|
+
border-radius: 12px;
|
|
531
|
+
font-size: 10px;
|
|
532
|
+
cursor: pointer;
|
|
533
|
+
transition: all 0.15s;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.refine-btn:hover {
|
|
537
|
+
background: var(--vscode-button-secondaryHoverBackground);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.refine-btn.active {
|
|
541
|
+
background: var(--vscode-button-background);
|
|
542
|
+
color: var(--vscode-button-foreground);
|
|
543
|
+
border-color: var(--vscode-button-background);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.input-wrapper {
|
|
547
|
+
display: flex;
|
|
548
|
+
gap: 8px;
|
|
549
|
+
align-items: flex-end;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.input-container {
|
|
553
|
+
flex: 1;
|
|
554
|
+
position: relative;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.prompt-input {
|
|
558
|
+
width: 100%;
|
|
559
|
+
min-height: 60px;
|
|
560
|
+
max-height: 200px;
|
|
561
|
+
padding: 12px;
|
|
562
|
+
padding-right: 40px;
|
|
563
|
+
border: 1px solid var(--vscode-input-border);
|
|
564
|
+
background: var(--vscode-input-background);
|
|
565
|
+
color: var(--vscode-input-foreground);
|
|
566
|
+
border-radius: 8px;
|
|
567
|
+
font-family: var(--vscode-font-family);
|
|
568
|
+
font-size: 13px;
|
|
569
|
+
line-height: 1.5;
|
|
570
|
+
resize: none;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.prompt-input:focus {
|
|
574
|
+
outline: none;
|
|
575
|
+
border-color: var(--vscode-focusBorder);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.context-indicator {
|
|
579
|
+
position: absolute;
|
|
580
|
+
top: 8px;
|
|
581
|
+
right: 8px;
|
|
582
|
+
font-size: 16px;
|
|
583
|
+
opacity: 0.5;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.send-btn {
|
|
587
|
+
padding: 12px 20px;
|
|
588
|
+
background: var(--vscode-button-background);
|
|
589
|
+
color: var(--vscode-button-foreground);
|
|
590
|
+
border: none;
|
|
591
|
+
border-radius: 8px;
|
|
592
|
+
font-size: 13px;
|
|
593
|
+
font-weight: 500;
|
|
594
|
+
cursor: pointer;
|
|
595
|
+
display: flex;
|
|
596
|
+
align-items: center;
|
|
597
|
+
gap: 6px;
|
|
598
|
+
transition: all 0.15s;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.send-btn:hover {
|
|
602
|
+
background: var(--vscode-button-hoverBackground);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.send-btn:disabled {
|
|
606
|
+
opacity: 0.6;
|
|
607
|
+
cursor: not-allowed;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.spinner {
|
|
611
|
+
display: inline-block;
|
|
612
|
+
width: 14px;
|
|
613
|
+
height: 14px;
|
|
614
|
+
border: 2px solid currentColor;
|
|
615
|
+
border-top-color: transparent;
|
|
616
|
+
border-radius: 50%;
|
|
617
|
+
animation: spin 0.8s linear infinite;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
@keyframes spin {
|
|
621
|
+
to { transform: rotate(360deg); }
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.shortcuts {
|
|
625
|
+
display: flex;
|
|
626
|
+
gap: 16px;
|
|
627
|
+
margin-top: 8px;
|
|
628
|
+
font-size: 10px;
|
|
629
|
+
color: var(--vscode-descriptionForeground);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.shortcut {
|
|
633
|
+
display: flex;
|
|
634
|
+
align-items: center;
|
|
635
|
+
gap: 4px;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
kbd {
|
|
639
|
+
background: var(--vscode-keybindingLabel-background);
|
|
640
|
+
border: 1px solid var(--vscode-keybindingLabel-border);
|
|
641
|
+
padding: 2px 6px;
|
|
642
|
+
border-radius: 4px;
|
|
643
|
+
font-family: var(--vscode-font-family);
|
|
644
|
+
font-size: 10px;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/* Loading indicator */
|
|
648
|
+
.typing-indicator {
|
|
649
|
+
display: none;
|
|
650
|
+
padding: 16px;
|
|
651
|
+
gap: 12px;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.typing-indicator.visible {
|
|
655
|
+
display: flex;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.typing-dots {
|
|
659
|
+
display: flex;
|
|
660
|
+
gap: 4px;
|
|
661
|
+
padding: 12px 16px;
|
|
662
|
+
background: var(--vscode-input-background);
|
|
663
|
+
border: 1px solid var(--vscode-widget-border);
|
|
664
|
+
border-radius: 12px;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.typing-dot {
|
|
668
|
+
width: 8px;
|
|
669
|
+
height: 8px;
|
|
670
|
+
background: var(--vscode-foreground);
|
|
671
|
+
border-radius: 50%;
|
|
672
|
+
opacity: 0.4;
|
|
673
|
+
animation: typingBounce 1.4s ease-in-out infinite;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
|
677
|
+
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
|
678
|
+
|
|
679
|
+
@keyframes typingBounce {
|
|
680
|
+
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
|
|
681
|
+
30% { transform: translateY(-8px); opacity: 1; }
|
|
682
|
+
}
|
|
683
|
+
</style>
|
|
684
|
+
</head>
|
|
685
|
+
<body>
|
|
686
|
+
<!-- Header -->
|
|
687
|
+
<div class="header">
|
|
688
|
+
<div class="header-left">
|
|
689
|
+
<span style="font-size: 18px;">✨</span>
|
|
690
|
+
<h1>PromptArchitect Chat</h1>
|
|
691
|
+
</div>
|
|
692
|
+
<div class="header-actions">
|
|
693
|
+
<button class="icon-btn" id="addContextBtn" title="Add editor context">📎</button>
|
|
694
|
+
<button class="icon-btn" id="exportBtn" title="Export chat">📤</button>
|
|
695
|
+
<button class="icon-btn" id="clearBtn" title="Clear chat">🗑️</button>
|
|
696
|
+
</div>
|
|
697
|
+
</div>
|
|
698
|
+
|
|
699
|
+
<!-- Chat Messages -->
|
|
700
|
+
<div class="chat-container" id="chatContainer">
|
|
701
|
+
<div class="empty-state" id="emptyState">
|
|
702
|
+
<div class="icon">💬</div>
|
|
703
|
+
<h2>Start a conversation</h2>
|
|
704
|
+
<p>Type a message below. Use the refine buttons to improve your prompt before sending.</p>
|
|
705
|
+
</div>
|
|
706
|
+
</div>
|
|
707
|
+
|
|
708
|
+
<!-- Typing Indicator -->
|
|
709
|
+
<div class="typing-indicator" id="typingIndicator">
|
|
710
|
+
<div class="avatar" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">🤖</div>
|
|
711
|
+
<div class="typing-dots">
|
|
712
|
+
<div class="typing-dot"></div>
|
|
713
|
+
<div class="typing-dot"></div>
|
|
714
|
+
<div class="typing-dot"></div>
|
|
715
|
+
</div>
|
|
716
|
+
</div>
|
|
717
|
+
|
|
718
|
+
<!-- Input Area -->
|
|
719
|
+
<div class="input-area">
|
|
720
|
+
<div class="refine-bar">
|
|
721
|
+
<span class="refine-label">✨ Refine first:</span>
|
|
722
|
+
<button class="refine-btn" data-mode="clarity">🎯 Clarity</button>
|
|
723
|
+
<button class="refine-btn" data-mode="detailed">📝 Detailed</button>
|
|
724
|
+
<button class="refine-btn" data-mode="concise">✂️ Concise</button>
|
|
725
|
+
<button class="refine-btn" data-mode="technical">⚙️ Technical</button>
|
|
726
|
+
<button class="refine-btn" data-mode="structured">📋 Structured</button>
|
|
727
|
+
</div>
|
|
728
|
+
|
|
729
|
+
<div class="input-wrapper">
|
|
730
|
+
<div class="input-container">
|
|
731
|
+
<textarea
|
|
732
|
+
class="prompt-input"
|
|
733
|
+
id="promptInput"
|
|
734
|
+
placeholder="Type your message... (Ctrl+Enter to send)"
|
|
735
|
+
rows="2"
|
|
736
|
+
></textarea>
|
|
737
|
+
<span class="context-indicator" id="contextIndicator" style="display: none;">📎</span>
|
|
738
|
+
</div>
|
|
739
|
+
<button class="send-btn" id="sendBtn">
|
|
740
|
+
<span id="sendBtnIcon">➤</span>
|
|
741
|
+
<span id="sendBtnText">Send</span>
|
|
742
|
+
</button>
|
|
743
|
+
</div>
|
|
744
|
+
|
|
745
|
+
<div class="shortcuts">
|
|
746
|
+
<span class="shortcut"><kbd>Ctrl</kbd>+<kbd>Enter</kbd> Send</span>
|
|
747
|
+
<span class="shortcut"><kbd>Shift</kbd>+<kbd>Enter</kbd> New line</span>
|
|
748
|
+
</div>
|
|
749
|
+
</div>
|
|
750
|
+
|
|
751
|
+
<script>
|
|
752
|
+
const vscode = acquireVsCodeApi();
|
|
753
|
+
const chatContainer = document.getElementById('chatContainer');
|
|
754
|
+
const emptyState = document.getElementById('emptyState');
|
|
755
|
+
const typingIndicator = document.getElementById('typingIndicator');
|
|
756
|
+
const input = document.getElementById('promptInput');
|
|
757
|
+
const sendBtn = document.getElementById('sendBtn');
|
|
758
|
+
const sendBtnIcon = document.getElementById('sendBtnIcon');
|
|
759
|
+
const sendBtnText = document.getElementById('sendBtnText');
|
|
760
|
+
const refineBtns = document.querySelectorAll('.refine-btn');
|
|
761
|
+
const addContextBtn = document.getElementById('addContextBtn');
|
|
762
|
+
const exportBtn = document.getElementById('exportBtn');
|
|
763
|
+
const clearBtn = document.getElementById('clearBtn');
|
|
764
|
+
const contextIndicator = document.getElementById('contextIndicator');
|
|
765
|
+
|
|
766
|
+
let isLoading = false;
|
|
767
|
+
let isRefining = false;
|
|
768
|
+
let wasRefined = false;
|
|
769
|
+
let attachedContext = null;
|
|
770
|
+
|
|
771
|
+
// Auto-resize textarea
|
|
772
|
+
input.addEventListener('input', () => {
|
|
773
|
+
input.style.height = 'auto';
|
|
774
|
+
input.style.height = Math.min(input.scrollHeight, 200) + 'px';
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
// Refine buttons
|
|
778
|
+
refineBtns.forEach(btn => {
|
|
779
|
+
btn.addEventListener('click', () => {
|
|
780
|
+
if (isRefining || !input.value.trim()) return;
|
|
781
|
+
vscode.postMessage({
|
|
782
|
+
command: 'refineBeforeSend',
|
|
783
|
+
text: input.value,
|
|
784
|
+
mode: btn.dataset.mode
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
// Send button
|
|
790
|
+
sendBtn.addEventListener('click', sendMessage);
|
|
791
|
+
|
|
792
|
+
function sendMessage() {
|
|
793
|
+
if (isLoading || isRefining || !input.value.trim()) return;
|
|
794
|
+
|
|
795
|
+
let messageText = input.value;
|
|
796
|
+
|
|
797
|
+
// If context is attached, prepend it
|
|
798
|
+
if (attachedContext) {
|
|
799
|
+
messageText = \`**Context from \${attachedContext.fileName} (\${attachedContext.language}):**
|
|
800
|
+
\\\`\\\`\\\`\${attachedContext.language}
|
|
801
|
+
\${attachedContext.code}
|
|
802
|
+
\\\`\\\`\\\`
|
|
803
|
+
|
|
804
|
+
\${messageText}\`;
|
|
805
|
+
attachedContext = null;
|
|
806
|
+
contextIndicator.style.display = 'none';
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
vscode.postMessage({
|
|
810
|
+
command: 'sendMessage',
|
|
811
|
+
text: messageText,
|
|
812
|
+
refined: wasRefined
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
input.value = '';
|
|
816
|
+
input.style.height = 'auto';
|
|
817
|
+
wasRefined = false;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Keyboard shortcuts
|
|
821
|
+
input.addEventListener('keydown', (e) => {
|
|
822
|
+
if (e.ctrlKey && e.key === 'Enter') {
|
|
823
|
+
e.preventDefault();
|
|
824
|
+
sendMessage();
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// Header actions
|
|
829
|
+
addContextBtn.addEventListener('click', () => {
|
|
830
|
+
vscode.postMessage({ command: 'addContext' });
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
exportBtn.addEventListener('click', () => {
|
|
834
|
+
vscode.postMessage({ command: 'exportChat' });
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
clearBtn.addEventListener('click', () => {
|
|
838
|
+
vscode.postMessage({ command: 'clearChat' });
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
// Render chat message
|
|
842
|
+
function renderMessage(msg) {
|
|
843
|
+
const div = document.createElement('div');
|
|
844
|
+
div.className = \`message \${msg.role}\`;
|
|
845
|
+
div.dataset.id = msg.id;
|
|
846
|
+
|
|
847
|
+
const avatar = msg.role === 'user' ? '👤' : '🤖';
|
|
848
|
+
const time = new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
849
|
+
|
|
850
|
+
// Simple markdown rendering
|
|
851
|
+
let content = msg.content
|
|
852
|
+
.replace(/\`\`\`(\w*)\n([\s\S]*?)\`\`\`/g, '<pre><code>$2</code></pre>')
|
|
853
|
+
.replace(/\`([^\`]+)\`/g, '<code>$1</code>')
|
|
854
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
855
|
+
.replace(/\n/g, '<br>');
|
|
856
|
+
|
|
857
|
+
div.innerHTML = \`
|
|
858
|
+
<div class="avatar">\${avatar}</div>
|
|
859
|
+
<div class="message-body">
|
|
860
|
+
<div class="message-content">\${content}</div>
|
|
861
|
+
<div class="message-meta">
|
|
862
|
+
<span>\${time}</span>
|
|
863
|
+
\${msg.refined ? '<span class="refined-badge">✨ Refined</span>' : ''}
|
|
864
|
+
</div>
|
|
865
|
+
<div class="message-actions">
|
|
866
|
+
<button class="msg-action-btn" onclick="copyMessage('\${msg.id}')">📋 Copy</button>
|
|
867
|
+
\${msg.role === 'assistant' ? '<button class="msg-action-btn" onclick="insertMessage(\\''+msg.id+'\\')">📥 Insert</button>' : ''}
|
|
868
|
+
</div>
|
|
869
|
+
</div>
|
|
870
|
+
\`;
|
|
871
|
+
|
|
872
|
+
return div;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Copy message
|
|
876
|
+
window.copyMessage = (id) => {
|
|
877
|
+
const msg = window.chatHistory?.find(m => m.id === id);
|
|
878
|
+
if (msg) {
|
|
879
|
+
vscode.postMessage({ command: 'copyToClipboard', text: msg.content });
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// Insert message into editor
|
|
884
|
+
window.insertMessage = (id) => {
|
|
885
|
+
const msg = window.chatHistory?.find(m => m.id === id);
|
|
886
|
+
if (msg) {
|
|
887
|
+
vscode.postMessage({ command: 'insertInEditor', text: msg.content });
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
// Handle messages from extension
|
|
892
|
+
window.addEventListener('message', event => {
|
|
893
|
+
const message = event.data;
|
|
894
|
+
|
|
895
|
+
switch (message.command) {
|
|
896
|
+
case 'prefill':
|
|
897
|
+
input.value = message.text;
|
|
898
|
+
input.focus();
|
|
899
|
+
input.style.height = 'auto';
|
|
900
|
+
input.style.height = Math.min(input.scrollHeight, 200) + 'px';
|
|
901
|
+
break;
|
|
902
|
+
|
|
903
|
+
case 'loading':
|
|
904
|
+
isLoading = message.loading;
|
|
905
|
+
sendBtn.disabled = message.loading;
|
|
906
|
+
typingIndicator.classList.toggle('visible', message.loading);
|
|
907
|
+
if (message.loading) {
|
|
908
|
+
sendBtnIcon.innerHTML = '<span class="spinner"></span>';
|
|
909
|
+
sendBtnText.textContent = '';
|
|
910
|
+
} else {
|
|
911
|
+
sendBtnIcon.textContent = '➤';
|
|
912
|
+
sendBtnText.textContent = 'Send';
|
|
913
|
+
}
|
|
914
|
+
// Scroll to bottom
|
|
915
|
+
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
916
|
+
break;
|
|
917
|
+
|
|
918
|
+
case 'refining':
|
|
919
|
+
isRefining = message.refining;
|
|
920
|
+
refineBtns.forEach(btn => btn.disabled = message.refining);
|
|
921
|
+
break;
|
|
922
|
+
|
|
923
|
+
case 'refined':
|
|
924
|
+
input.value = message.text;
|
|
925
|
+
wasRefined = true;
|
|
926
|
+
input.style.height = 'auto';
|
|
927
|
+
input.style.height = Math.min(input.scrollHeight, 200) + 'px';
|
|
928
|
+
break;
|
|
929
|
+
|
|
930
|
+
case 'refineError':
|
|
931
|
+
// Could show inline error
|
|
932
|
+
break;
|
|
933
|
+
|
|
934
|
+
case 'syncHistory':
|
|
935
|
+
window.chatHistory = message.history;
|
|
936
|
+
|
|
937
|
+
// Clear and re-render
|
|
938
|
+
chatContainer.innerHTML = '';
|
|
939
|
+
|
|
940
|
+
if (message.history.length === 0) {
|
|
941
|
+
chatContainer.appendChild(emptyState);
|
|
942
|
+
} else {
|
|
943
|
+
emptyState.remove();
|
|
944
|
+
message.history.forEach(msg => {
|
|
945
|
+
chatContainer.appendChild(renderMessage(msg));
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Scroll to bottom
|
|
950
|
+
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
951
|
+
break;
|
|
952
|
+
|
|
953
|
+
case 'addContext':
|
|
954
|
+
attachedContext = message.context;
|
|
955
|
+
contextIndicator.style.display = 'block';
|
|
956
|
+
contextIndicator.title = \`Attached: \${message.context.fileName}\`;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
// Focus input on load
|
|
962
|
+
input.focus();
|
|
963
|
+
</script>
|
|
964
|
+
</body>
|
|
965
|
+
</html>
|
|
966
|
+
`;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
private dispose() {
|
|
970
|
+
ChatPanel.currentPanel = undefined;
|
|
971
|
+
this.panel.dispose();
|
|
972
|
+
while (this.disposables.length) {
|
|
973
|
+
const disposable = this.disposables.pop();
|
|
974
|
+
if (disposable) {
|
|
975
|
+
disposable.dispose();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|